aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock119
-rw-r--r--crates/assists/src/assist_context.rs38
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs284
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs92
-rw-r--r--crates/assists/src/handlers/auto_import.rs3
-rw-r--r--crates/assists/src/handlers/change_return_type_to_result.rs1091
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs76
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs180
-rw-r--r--crates/assists/src/handlers/ignore_test.rs103
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs71
-rw-r--r--crates/assists/src/handlers/remove_unused_param.rs83
-rw-r--r--crates/assists/src/handlers/reorder_fields.rs4
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs400
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/assists/src/handlers/unwrap_block.rs613
-rw-r--r--crates/assists/src/handlers/wrap_return_type_in_result.rs1158
-rw-r--r--crates/assists/src/lib.rs10
-rw-r--r--crates/assists/src/tests.rs25
-rw-r--r--crates/assists/src/tests/generated.rs88
-rw-r--r--crates/assists/src/utils.rs125
-rw-r--r--crates/assists/src/utils/import_assets.rs34
-rw-r--r--crates/assists/src/utils/insert_use.rs112
-rw-r--r--crates/base_db/src/input.rs20
-rw-r--r--crates/cfg/src/lib.rs6
-rw-r--r--crates/completion/Cargo.toml2
-rw-r--r--crates/completion/src/completions.rs8
-rw-r--r--crates/completion/src/completions/keyword.rs46
-rw-r--r--crates/completion/src/completions/postfix.rs12
-rw-r--r--crates/completion/src/completions/record.rs108
-rw-r--r--crates/completion/src/completions/unqualified_path.rs132
-rw-r--r--crates/completion/src/config.rs6
-rw-r--r--crates/completion/src/item.rs59
-rw-r--r--crates/completion/src/lib.rs7
-rw-r--r--crates/completion/src/render.rs57
-rw-r--r--crates/completion/src/render/enum_variant.rs10
-rw-r--r--crates/completion/src/render/function.rs12
-rw-r--r--crates/completion/src/render/macro_.rs12
-rw-r--r--crates/hir/src/code_model.rs10
-rw-r--r--crates/hir/src/db.rs2
-rw-r--r--crates/hir/src/diagnostics.rs4
-rw-r--r--crates/hir/src/lib.rs5
-rw-r--r--crates/hir_expand/src/db.rs141
-rw-r--r--crates/hir_expand/src/diagnostics.rs2
-rw-r--r--crates/hir_expand/src/lib.rs2
-rw-r--r--crates/hir_ty/Cargo.toml1
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs248
-rw-r--r--crates/hir_ty/src/infer/pat.rs46
-rw-r--r--crates/hir_ty/src/tests.rs7
-rw-r--r--crates/hir_ty/src/tests/patterns.rs95
-rw-r--r--crates/ide/src/diagnostics.rs40
-rw-r--r--crates/ide/src/fn_references.rs5
-rw-r--r--crates/ide/src/references.rs25
-rw-r--r--crates/ide/src/references/rename.rs176
-rw-r--r--crates/ide/src/runnables.rs19
-rw-r--r--crates/ide/src/status.rs10
-rw-r--r--crates/ide/src/syntax_highlighting.rs9
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html56
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html8
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html12
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html30
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html6
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs30
-rw-r--r--crates/ide_db/src/apply_change.rs4
-rw-r--r--crates/ide_db/src/imports_locator.rs60
-rw-r--r--crates/ide_db/src/lib.rs2
-rw-r--r--crates/ide_db/src/search.rs20
-rw-r--r--crates/parser/src/grammar/items.rs10
-rw-r--r--crates/project_model/src/cargo_workspace.rs23
-rw-r--r--crates/project_model/src/lib.rs469
-rw-r--r--crates/project_model/src/sysroot.rs2
-rw-r--r--crates/project_model/src/workspace.rs552
-rw-r--r--crates/rust-analyzer/Cargo.toml4
-rw-r--r--crates/rust-analyzer/src/caps.rs7
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs45
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt46
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt28
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt36
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt28
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt28
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt20
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt20
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt20
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt37
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt48
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs41
-rw-r--r--crates/rust-analyzer/src/document.rs6
-rw-r--r--crates/rust-analyzer/src/global_state.rs4
-rw-r--r--crates/rust-analyzer/src/handlers.rs61
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs51
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs6
-rw-r--r--crates/rust-analyzer/src/main_loop.rs15
-rw-r--r--crates/rust-analyzer/src/reload.rs17
-rw-r--r--crates/rust-analyzer/src/to_proto.rs66
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/support.rs12
-rw-r--r--crates/stdx/src/lib.rs30
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/algo.rs130
-rw-r--r--crates/syntax/src/ast.rs19
-rw-r--r--crates/syntax/src/ast/expr_ext.rs12
-rw-r--r--crates/syntax/src/ast/make.rs4
-rw-r--r--crates/syntax/src/ast/node_ext.rs10
-rw-r--r--crates/syntax/src/ast/token_ext.rs73
-rw-r--r--crates/syntax/src/parsing/text_tree_sink.rs27
-rw-r--r--crates/syntax/test_data/parser/ok/0037_mod.rast6
-rw-r--r--crates/syntax/test_data/parser/ok/0068_item_modifiers.rast16
-rw-r--r--crates/syntax/test_data/parser/ok/0068_item_modifiers.rs2
-rw-r--r--docs/dev/lsp-extensions.md55
-rw-r--r--docs/dev/syntax.md4
-rw-r--r--editors/code/package-lock.json22
-rw-r--r--editors/code/package.json36
-rw-r--r--editors/code/rust.tmGrammar.json31
-rw-r--r--editors/code/src/client.ts23
-rw-r--r--editors/code/src/commands.ts32
-rw-r--r--editors/code/src/lsp_ext.ts12
-rw-r--r--editors/code/src/main.ts1
-rw-r--r--editors/code/src/snippets.ts23
-rw-r--r--xtask/tests/tidy.rs8
122 files changed, 5511 insertions, 3062 deletions
diff --git a/.gitignore b/.gitignore
index b205bf3fb..7e097c015 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ crates/*/target
10generated_assists.adoc 10generated_assists.adoc
11generated_features.adoc 11generated_features.adoc
12generated_diagnostic.adoc 12generated_diagnostic.adoc
13.DS_Store
diff --git a/Cargo.lock b/Cargo.lock
index 1a4a63550..051d9e734 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -81,9 +81,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
81 81
82[[package]] 82[[package]]
83name = "backtrace" 83name = "backtrace"
84version = "0.3.54" 84version = "0.3.55"
85source = "registry+https://github.com/rust-lang/crates.io-index" 85source = "registry+https://github.com/rust-lang/crates.io-index"
86checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28" 86checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
87dependencies = [ 87dependencies = [
88 "addr2line", 88 "addr2line",
89 "cfg-if 1.0.0", 89 "cfg-if 1.0.0",
@@ -139,9 +139,9 @@ dependencies = [
139 139
140[[package]] 140[[package]]
141name = "cc" 141name = "cc"
142version = "1.0.61" 142version = "1.0.65"
143source = "registry+https://github.com/rust-lang/crates.io-index" 143source = "registry+https://github.com/rust-lang/crates.io-index"
144checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" 144checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
145 145
146[[package]] 146[[package]]
147name = "cfg" 147name = "cfg"
@@ -242,9 +242,9 @@ dependencies = [
242 242
243[[package]] 243[[package]]
244name = "cmake" 244name = "cmake"
245version = "0.1.44" 245version = "0.1.45"
246source = "registry+https://github.com/rust-lang/crates.io-index" 246source = "registry+https://github.com/rust-lang/crates.io-index"
247checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" 247checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855"
248dependencies = [ 248dependencies = [
249 "cc", 249 "cc",
250] 250]
@@ -253,7 +253,9 @@ dependencies = [
253name = "completion" 253name = "completion"
254version = "0.0.0" 254version = "0.0.0"
255dependencies = [ 255dependencies = [
256 "assists",
256 "base_db", 257 "base_db",
258 "either",
257 "expect-test", 259 "expect-test",
258 "hir", 260 "hir",
259 "ide_db", 261 "ide_db",
@@ -379,9 +381,9 @@ dependencies = [
379 381
380[[package]] 382[[package]]
381name = "env_logger" 383name = "env_logger"
382version = "0.8.1" 384version = "0.8.2"
383source = "registry+https://github.com/rust-lang/crates.io-index" 385source = "registry+https://github.com/rust-lang/crates.io-index"
384checksum = "54532e3223c5af90a6a757c90b5c5521564b07e5e7a958681bcd2afad421cdcd" 386checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
385dependencies = [ 387dependencies = [
386 "log", 388 "log",
387] 389]
@@ -398,11 +400,11 @@ dependencies = [
398 400
399[[package]] 401[[package]]
400name = "filetime" 402name = "filetime"
401version = "0.2.12" 403version = "0.2.13"
402source = "registry+https://github.com/rust-lang/crates.io-index" 404source = "registry+https://github.com/rust-lang/crates.io-index"
403checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e" 405checksum = "0c122a393ea57648015bf06fbd3d372378992e86b9ff5a7a497b076a28c79efe"
404dependencies = [ 406dependencies = [
405 "cfg-if 0.1.10", 407 "cfg-if 1.0.0",
406 "libc", 408 "libc",
407 "redox_syscall", 409 "redox_syscall",
408 "winapi 0.3.9", 410 "winapi 0.3.9",
@@ -439,6 +441,16 @@ dependencies = [
439] 441]
440 442
441[[package]] 443[[package]]
444name = "form_urlencoded"
445version = "1.0.0"
446source = "registry+https://github.com/rust-lang/crates.io-index"
447checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
448dependencies = [
449 "matches",
450 "percent-encoding",
451]
452
453[[package]]
442name = "fsevent" 454name = "fsevent"
443version = "2.0.2" 455version = "2.0.2"
444source = "registry+https://github.com/rust-lang/crates.io-index" 456source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -612,6 +624,7 @@ dependencies = [
612 "hir_expand", 624 "hir_expand",
613 "itertools", 625 "itertools",
614 "log", 626 "log",
627 "once_cell",
615 "profile", 628 "profile",
616 "rustc-hash", 629 "rustc-hash",
617 "scoped-tls", 630 "scoped-tls",
@@ -714,18 +727,18 @@ dependencies = [
714 727
715[[package]] 728[[package]]
716name = "inotify-sys" 729name = "inotify-sys"
717version = "0.1.3" 730version = "0.1.4"
718source = "registry+https://github.com/rust-lang/crates.io-index" 731source = "registry+https://github.com/rust-lang/crates.io-index"
719checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" 732checksum = "c4563555856585ab3180a5bf0b2f9f8d301a728462afffc8195b3f5394229c55"
720dependencies = [ 733dependencies = [
721 "libc", 734 "libc",
722] 735]
723 736
724[[package]] 737[[package]]
725name = "instant" 738name = "instant"
726version = "0.1.8" 739version = "0.1.9"
727source = "registry+https://github.com/rust-lang/crates.io-index" 740source = "registry+https://github.com/rust-lang/crates.io-index"
728checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" 741checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
729dependencies = [ 742dependencies = [
730 "cfg-if 1.0.0", 743 "cfg-if 1.0.0",
731] 744]
@@ -809,9 +822,9 @@ dependencies = [
809 822
810[[package]] 823[[package]]
811name = "lock_api" 824name = "lock_api"
812version = "0.4.1" 825version = "0.4.2"
813source = "registry+https://github.com/rust-lang/crates.io-index" 826source = "registry+https://github.com/rust-lang/crates.io-index"
814checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" 827checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
815dependencies = [ 828dependencies = [
816 "scopeguard", 829 "scopeguard",
817] 830]
@@ -840,9 +853,9 @@ dependencies = [
840 853
841[[package]] 854[[package]]
842name = "lsp-server" 855name = "lsp-server"
843version = "0.4.1" 856version = "0.5.0"
844source = "registry+https://github.com/rust-lang/crates.io-index" 857source = "registry+https://github.com/rust-lang/crates.io-index"
845checksum = "9c85acaf36c53bf15da2b8b35afeea56747707261f59eb0b77229081dd72b04e" 858checksum = "69b18dfe0e4a380b872aa79d8e0ee6c3d7a9682466e84b83ad807c88b3545f79"
846dependencies = [ 859dependencies = [
847 "crossbeam-channel 0.5.0", 860 "crossbeam-channel 0.5.0",
848 "log", 861 "log",
@@ -852,9 +865,9 @@ dependencies = [
852 865
853[[package]] 866[[package]]
854name = "lsp-types" 867name = "lsp-types"
855version = "0.83.0" 868version = "0.84.0"
856source = "registry+https://github.com/rust-lang/crates.io-index" 869source = "registry+https://github.com/rust-lang/crates.io-index"
857checksum = "25e0bd4b95038f2c23bda332ba0ca684e8dda765db1f9bdb63dc4c3e01f3b456" 870checksum = "3b95be71fe205e44de754185bcf86447b65813ce1ceb298f8d3793ade5fff08d"
858dependencies = [ 871dependencies = [
859 "base64", 872 "base64",
860 "bitflags", 873 "bitflags",
@@ -1053,21 +1066,21 @@ checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
1053 1066
1054[[package]] 1067[[package]]
1055name = "once_cell" 1068name = "once_cell"
1056version = "1.4.1" 1069version = "1.5.2"
1057source = "registry+https://github.com/rust-lang/crates.io-index" 1070source = "registry+https://github.com/rust-lang/crates.io-index"
1058checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" 1071checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
1059 1072
1060[[package]] 1073[[package]]
1061name = "oorandom" 1074name = "oorandom"
1062version = "11.1.2" 1075version = "11.1.3"
1063source = "registry+https://github.com/rust-lang/crates.io-index" 1076source = "registry+https://github.com/rust-lang/crates.io-index"
1064checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" 1077checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
1065 1078
1066[[package]] 1079[[package]]
1067name = "parking_lot" 1080name = "parking_lot"
1068version = "0.11.0" 1081version = "0.11.1"
1069source = "registry+https://github.com/rust-lang/crates.io-index" 1082source = "registry+https://github.com/rust-lang/crates.io-index"
1070checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" 1083checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
1071dependencies = [ 1084dependencies = [
1072 "instant", 1085 "instant",
1073 "lock_api", 1086 "lock_api",
@@ -1152,9 +1165,9 @@ checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1"
1152 1165
1153[[package]] 1166[[package]]
1154name = "pin-project-lite" 1167name = "pin-project-lite"
1155version = "0.1.11" 1168version = "0.2.0"
1156source = "registry+https://github.com/rust-lang/crates.io-index" 1169source = "registry+https://github.com/rust-lang/crates.io-index"
1157checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" 1170checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
1158 1171
1159[[package]] 1172[[package]]
1160name = "plain" 1173name = "plain"
@@ -1383,9 +1396,9 @@ dependencies = [
1383 1396
1384[[package]] 1397[[package]]
1385name = "rustc-ap-rustc_lexer" 1398name = "rustc-ap-rustc_lexer"
1386version = "686.0.0" 1399version = "688.0.0"
1387source = "registry+https://github.com/rust-lang/crates.io-index" 1400source = "registry+https://github.com/rust-lang/crates.io-index"
1388checksum = "a5b04cd2159495584d976d501c5394498470c2e94e4f0cebb8186562d407a678" 1401checksum = "ebbdcc99bd015349093fcbae4780fda21416fec5d8843acfb3d1733e130cd4db"
1389dependencies = [ 1402dependencies = [
1390 "unicode-xid", 1403 "unicode-xid",
1391] 1404]
@@ -1581,9 +1594,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1581 1594
1582[[package]] 1595[[package]]
1583name = "smallvec" 1596name = "smallvec"
1584version = "1.4.2" 1597version = "1.5.0"
1585source = "registry+https://github.com/rust-lang/crates.io-index" 1598source = "registry+https://github.com/rust-lang/crates.io-index"
1586checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" 1599checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
1587 1600
1588[[package]] 1601[[package]]
1589name = "smol_str" 1602name = "smol_str"
@@ -1614,9 +1627,9 @@ version = "0.0.0"
1614 1627
1615[[package]] 1628[[package]]
1616name = "syn" 1629name = "syn"
1617version = "1.0.48" 1630version = "1.0.51"
1618source = "registry+https://github.com/rust-lang/crates.io-index" 1631source = "registry+https://github.com/rust-lang/crates.io-index"
1619checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" 1632checksum = "3b4f34193997d92804d359ed09953e25d5138df6bcc055a71bf68ee89fdf9223"
1620dependencies = [ 1633dependencies = [
1621 "proc-macro2", 1634 "proc-macro2",
1622 "quote", 1635 "quote",
@@ -1659,9 +1672,9 @@ dependencies = [
1659 1672
1660[[package]] 1673[[package]]
1661name = "termcolor" 1674name = "termcolor"
1662version = "1.1.0" 1675version = "1.1.2"
1663source = "registry+https://github.com/rust-lang/crates.io-index" 1676source = "registry+https://github.com/rust-lang/crates.io-index"
1664checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 1677checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
1665dependencies = [ 1678dependencies = [
1666 "winapi-util", 1679 "winapi-util",
1667] 1680]
@@ -1727,9 +1740,18 @@ dependencies = [
1727 1740
1728[[package]] 1741[[package]]
1729name = "tinyvec" 1742name = "tinyvec"
1730version = "0.3.4" 1743version = "1.1.0"
1731source = "registry+https://github.com/rust-lang/crates.io-index" 1744source = "registry+https://github.com/rust-lang/crates.io-index"
1732checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" 1745checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
1746dependencies = [
1747 "tinyvec_macros",
1748]
1749
1750[[package]]
1751name = "tinyvec_macros"
1752version = "0.1.0"
1753source = "registry+https://github.com/rust-lang/crates.io-index"
1754checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1733 1755
1734[[package]] 1756[[package]]
1735name = "toolchain" 1757name = "toolchain"
@@ -1740,11 +1762,11 @@ dependencies = [
1740 1762
1741[[package]] 1763[[package]]
1742name = "tracing" 1764name = "tracing"
1743version = "0.1.21" 1765version = "0.1.22"
1744source = "registry+https://github.com/rust-lang/crates.io-index" 1766source = "registry+https://github.com/rust-lang/crates.io-index"
1745checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27" 1767checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
1746dependencies = [ 1768dependencies = [
1747 "cfg-if 0.1.10", 1769 "cfg-if 1.0.0",
1748 "pin-project-lite", 1770 "pin-project-lite",
1749 "tracing-attributes", 1771 "tracing-attributes",
1750 "tracing-core", 1772 "tracing-core",
@@ -1867,18 +1889,18 @@ dependencies = [
1867 1889
1868[[package]] 1890[[package]]
1869name = "unicode-normalization" 1891name = "unicode-normalization"
1870version = "0.1.13" 1892version = "0.1.16"
1871source = "registry+https://github.com/rust-lang/crates.io-index" 1893source = "registry+https://github.com/rust-lang/crates.io-index"
1872checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" 1894checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
1873dependencies = [ 1895dependencies = [
1874 "tinyvec", 1896 "tinyvec",
1875] 1897]
1876 1898
1877[[package]] 1899[[package]]
1878name = "unicode-segmentation" 1900name = "unicode-segmentation"
1879version = "1.6.0" 1901version = "1.7.0"
1880source = "registry+https://github.com/rust-lang/crates.io-index" 1902source = "registry+https://github.com/rust-lang/crates.io-index"
1881checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 1903checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae"
1882 1904
1883[[package]] 1905[[package]]
1884name = "unicode-xid" 1906name = "unicode-xid"
@@ -1888,10 +1910,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1888 1910
1889[[package]] 1911[[package]]
1890name = "url" 1912name = "url"
1891version = "2.1.1" 1913version = "2.2.0"
1892source = "registry+https://github.com/rust-lang/crates.io-index" 1914source = "registry+https://github.com/rust-lang/crates.io-index"
1893checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1915checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
1894dependencies = [ 1916dependencies = [
1917 "form_urlencoded",
1895 "idna", 1918 "idna",
1896 "matches", 1919 "matches",
1897 "percent-encoding", 1920 "percent-encoding",
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index fcfe2d6ee..69499ea32 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -208,7 +208,7 @@ pub(crate) struct AssistBuilder {
208 edit: TextEditBuilder, 208 edit: TextEditBuilder,
209 file_id: FileId, 209 file_id: FileId,
210 is_snippet: bool, 210 is_snippet: bool,
211 change: SourceChange, 211 source_file_edits: Vec<SourceFileEdit>,
212} 212}
213 213
214impl AssistBuilder { 214impl AssistBuilder {
@@ -217,20 +217,27 @@ impl AssistBuilder {
217 edit: TextEdit::builder(), 217 edit: TextEdit::builder(),
218 file_id, 218 file_id,
219 is_snippet: false, 219 is_snippet: false,
220 change: SourceChange::default(), 220 source_file_edits: Vec::default(),
221 } 221 }
222 } 222 }
223 223
224 pub(crate) fn edit_file(&mut self, file_id: FileId) { 224 pub(crate) fn edit_file(&mut self, file_id: FileId) {
225 self.commit();
225 self.file_id = file_id; 226 self.file_id = file_id;
226 } 227 }
227 228
228 fn commit(&mut self) { 229 fn commit(&mut self) {
229 let edit = mem::take(&mut self.edit).finish(); 230 let edit = mem::take(&mut self.edit).finish();
230 if !edit.is_empty() { 231 if !edit.is_empty() {
231 let new_edit = SourceFileEdit { file_id: self.file_id, edit }; 232 match self.source_file_edits.binary_search_by_key(&self.file_id, |edit| edit.file_id) {
232 assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id)); 233 Ok(idx) => self.source_file_edits[idx]
233 self.change.source_file_edits.push(new_edit); 234 .edit
235 .union(edit)
236 .expect("overlapping edits for same file"),
237 Err(idx) => self
238 .source_file_edits
239 .insert(idx, SourceFileEdit { file_id: self.file_id, edit }),
240 }
234 } 241 }
235 } 242 }
236 243
@@ -270,23 +277,18 @@ impl AssistBuilder {
270 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) 277 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
271 } 278 }
272 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { 279 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
273 let node = rewriter.rewrite_root().unwrap(); 280 if let Some(node) = rewriter.rewrite_root() {
274 let new = rewriter.rewrite(&node); 281 let new = rewriter.rewrite(&node);
275 algo::diff(&node, &new).into_text_edit(&mut self.edit); 282 algo::diff(&node, &new).into_text_edit(&mut self.edit);
276 } 283 }
277
278 // FIXME: kill this API
279 /// Get access to the raw `TextEditBuilder`.
280 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
281 &mut self.edit
282 } 284 }
283 285
284 fn finish(mut self) -> SourceChange { 286 fn finish(mut self) -> SourceChange {
285 self.commit(); 287 self.commit();
286 let mut change = mem::take(&mut self.change); 288 SourceChange {
287 if self.is_snippet { 289 source_file_edits: mem::take(&mut self.source_file_edits),
288 change.is_snippet = true; 290 file_system_edits: Default::default(),
291 is_snippet: self.is_snippet,
289 } 292 }
290 change
291 } 293 }
292} 294}
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
deleted file mode 100644
index 669dd9b21..000000000
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ /dev/null
@@ -1,284 +0,0 @@
1use ide_db::imports_locator;
2use itertools::Itertools;
3use syntax::{
4 ast::{self, make, AstNode},
5 Direction, SmolStr,
6 SyntaxKind::{IDENT, WHITESPACE},
7 TextRange, TextSize,
8};
9
10use crate::{
11 assist_config::SnippetCap,
12 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::mod_path_to_ast,
14 AssistId, AssistKind,
15};
16
17// Assist: add_custom_impl
18//
19// Adds impl block for derived trait.
20//
21// ```
22// #[derive(Deb<|>ug, Display)]
23// struct S;
24// ```
25// ->
26// ```
27// #[derive(Display)]
28// struct S;
29//
30// impl Debug for S {
31// $0
32// }
33// ```
34pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
36
37 let attr_name = attr
38 .syntax()
39 .descendants_with_tokens()
40 .filter(|t| t.kind() == IDENT)
41 .find_map(syntax::NodeOrToken::into_token)
42 .filter(|t| t.text() == "derive")?
43 .text()
44 .clone();
45
46 let trait_token =
47 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
48 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
49
50 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
51 let annotated_name = annotated.syntax().text().to_string();
52 let insert_pos = annotated.syntax().parent()?.text_range().end();
53
54 let current_module = ctx.sema.scope(annotated.syntax()).module()?;
55 let current_crate = current_module.krate();
56
57 let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
58 .into_iter()
59 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
60 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
61 _ => None,
62 })
63 .flat_map(|trait_| {
64 current_module
65 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
66 .as_ref()
67 .map(mod_path_to_ast)
68 .zip(Some(trait_))
69 });
70
71 let mut no_traits_found = true;
72 for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
73 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
74 }
75 if no_traits_found {
76 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
77 }
78 Some(())
79}
80
81fn add_assist(
82 acc: &mut Assists,
83 snippet_cap: Option<SnippetCap>,
84 attr: &ast::Attr,
85 trait_path: &ast::Path,
86 annotated_name: &str,
87 insert_pos: TextSize,
88) -> Option<()> {
89 let target = attr.syntax().text_range();
90 let input = attr.token_tree()?;
91 let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
92 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
93
94 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
95 update_attribute(builder, &input, &trait_name, &attr);
96 match snippet_cap {
97 Some(cap) => {
98 builder.insert_snippet(
99 cap,
100 insert_pos,
101 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
102 );
103 }
104 None => {
105 builder.insert(
106 insert_pos,
107 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
108 );
109 }
110 }
111 })
112}
113
114fn update_attribute(
115 builder: &mut AssistBuilder,
116 input: &ast::TokenTree,
117 trait_name: &ast::NameRef,
118 attr: &ast::Attr,
119) {
120 let new_attr_input = input
121 .syntax()
122 .descendants_with_tokens()
123 .filter(|t| t.kind() == IDENT)
124 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
125 .filter(|t| t != trait_name.text())
126 .collect::<Vec<SmolStr>>();
127 let has_more_derives = !new_attr_input.is_empty();
128
129 if has_more_derives {
130 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
131 builder.replace(input.syntax().text_range(), new_attr_input);
132 } else {
133 let attr_range = attr.syntax().text_range();
134 builder.delete(attr_range);
135
136 let line_break_range = attr
137 .syntax()
138 .next_sibling_or_token()
139 .filter(|t| t.kind() == WHITESPACE)
140 .map(|t| t.text_range())
141 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
142 builder.delete(line_break_range);
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use crate::tests::{check_assist, check_assist_not_applicable};
149
150 use super::*;
151
152 #[test]
153 fn add_custom_impl_qualified() {
154 check_assist(
155 add_custom_impl,
156 "
157mod fmt {
158 pub trait Debug {}
159}
160
161#[derive(Debu<|>g)]
162struct Foo {
163 bar: String,
164}
165",
166 "
167mod fmt {
168 pub trait Debug {}
169}
170
171struct Foo {
172 bar: String,
173}
174
175impl fmt::Debug for Foo {
176 $0
177}
178",
179 )
180 }
181 #[test]
182 fn add_custom_impl_for_unique_input() {
183 check_assist(
184 add_custom_impl,
185 "
186#[derive(Debu<|>g)]
187struct Foo {
188 bar: String,
189}
190 ",
191 "
192struct Foo {
193 bar: String,
194}
195
196impl Debug for Foo {
197 $0
198}
199 ",
200 )
201 }
202
203 #[test]
204 fn add_custom_impl_for_with_visibility_modifier() {
205 check_assist(
206 add_custom_impl,
207 "
208#[derive(Debug<|>)]
209pub struct Foo {
210 bar: String,
211}
212 ",
213 "
214pub struct Foo {
215 bar: String,
216}
217
218impl Debug for Foo {
219 $0
220}
221 ",
222 )
223 }
224
225 #[test]
226 fn add_custom_impl_when_multiple_inputs() {
227 check_assist(
228 add_custom_impl,
229 "
230#[derive(Display, Debug<|>, Serialize)]
231struct Foo {}
232 ",
233 "
234#[derive(Display, Serialize)]
235struct Foo {}
236
237impl Debug for Foo {
238 $0
239}
240 ",
241 )
242 }
243
244 #[test]
245 fn test_ignore_derive_macro_without_input() {
246 check_assist_not_applicable(
247 add_custom_impl,
248 "
249#[derive(<|>)]
250struct Foo {}
251 ",
252 )
253 }
254
255 #[test]
256 fn test_ignore_if_cursor_on_param() {
257 check_assist_not_applicable(
258 add_custom_impl,
259 "
260#[derive<|>(Debug)]
261struct Foo {}
262 ",
263 );
264
265 check_assist_not_applicable(
266 add_custom_impl,
267 "
268#[derive(Debug)<|>]
269struct Foo {}
270 ",
271 )
272 }
273
274 #[test]
275 fn test_ignore_if_not_derive() {
276 check_assist_not_applicable(
277 add_custom_impl,
278 "
279#[allow(non_camel_<|>case_types)]
280struct Foo {}
281 ",
282 )
283 }
284}
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index b82fb30ad..bbb71e261 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -1,27 +1,14 @@
1use hir::HasSource; 1use ide_db::traits::resolve_target_trait;
2use ide_db::traits::{get_missing_assoc_items, resolve_target_trait}; 2use syntax::ast::{self, AstNode};
3use syntax::{
4 ast::{
5 self,
6 edit::{self, AstNodeEdit, IndentLevel},
7 make, AstNode, NameOwner,
8 },
9 SmolStr,
10};
11 3
12use crate::{ 4use crate::{
13 assist_context::{AssistContext, Assists}, 5 assist_context::{AssistContext, Assists},
14 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 6 utils::add_trait_assoc_items_to_impl,
15 utils::{render_snippet, Cursor}, 7 utils::DefaultMethods,
8 utils::{filter_assoc_items, render_snippet, Cursor},
16 AssistId, AssistKind, 9 AssistId, AssistKind,
17}; 10};
18 11
19#[derive(PartialEq)]
20enum AddMissingImplMembersMode {
21 DefaultMethodsOnly,
22 NoDefaultMethods,
23}
24
25// Assist: add_impl_missing_members 12// Assist: add_impl_missing_members
26// 13//
27// Adds scaffold for required impl members. 14// Adds scaffold for required impl members.
@@ -55,7 +42,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
55 add_missing_impl_members_inner( 42 add_missing_impl_members_inner(
56 acc, 43 acc,
57 ctx, 44 ctx,
58 AddMissingImplMembersMode::NoDefaultMethods, 45 DefaultMethods::No,
59 "add_impl_missing_members", 46 "add_impl_missing_members",
60 "Implement missing members", 47 "Implement missing members",
61 ) 48 )
@@ -97,7 +84,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
97 add_missing_impl_members_inner( 84 add_missing_impl_members_inner(
98 acc, 85 acc,
99 ctx, 86 ctx,
100 AddMissingImplMembersMode::DefaultMethodsOnly, 87 DefaultMethods::Only,
101 "add_impl_default_members", 88 "add_impl_default_members",
102 "Implement default members", 89 "Implement default members",
103 ) 90 )
@@ -106,7 +93,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
106fn add_missing_impl_members_inner( 93fn add_missing_impl_members_inner(
107 acc: &mut Assists, 94 acc: &mut Assists,
108 ctx: &AssistContext, 95 ctx: &AssistContext,
109 mode: AddMissingImplMembersMode, 96 mode: DefaultMethods,
110 assist_id: &'static str, 97 assist_id: &'static str,
111 label: &'static str, 98 label: &'static str,
112) -> Option<()> { 99) -> Option<()> {
@@ -114,32 +101,11 @@ fn add_missing_impl_members_inner(
114 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; 101 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
115 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; 102 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
116 103
117 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 104 let missing_items = filter_assoc_items(
118 match item { 105 ctx.db(),
119 ast::AssocItem::Fn(def) => def.name(), 106 &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
120 ast::AssocItem::TypeAlias(def) => def.name(), 107 mode,
121 ast::AssocItem::Const(def) => def.name(), 108 );
122 ast::AssocItem::MacroCall(_) => None,
123 }
124 .map(|it| it.text().clone())
125 };
126
127 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
128 .iter()
129 .map(|i| match i {
130 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
131 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
132 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
133 })
134 .filter(|t| def_name(&t).is_some())
135 .filter(|t| match t {
136 ast::AssocItem::Fn(def) => match mode {
137 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
138 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
139 },
140 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
141 })
142 .collect::<Vec<_>>();
143 109
144 if missing_items.is_empty() { 110 if missing_items.is_empty() {
145 return None; 111 return None;
@@ -147,29 +113,9 @@ fn add_missing_impl_members_inner(
147 113
148 let target = impl_def.syntax().text_range(); 114 let target = impl_def.syntax().text_range();
149 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { 115 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
150 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
151
152 let n_existing_items = impl_item_list.assoc_items().count();
153 let source_scope = ctx.sema.scope_for_def(trait_);
154 let target_scope = ctx.sema.scope(impl_def.syntax()); 116 let target_scope = ctx.sema.scope(impl_def.syntax());
155 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 117 let (new_impl_def, first_new_item) =
156 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); 118 add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
157
158 let items = missing_items
159 .into_iter()
160 .map(|it| ast_transform::apply(&*ast_transform, it))
161 .map(|it| match it {
162 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
163 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
164 _ => it,
165 })
166 .map(|it| edit::remove_attrs_and_docs(&it));
167
168 let new_impl_item_list = impl_item_list.append_items(items);
169 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
170 let first_new_item =
171 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
172
173 match ctx.config.snippet_cap { 119 match ctx.config.snippet_cap {
174 None => builder.replace(target, new_impl_def.to_string()), 120 None => builder.replace(target, new_impl_def.to_string()),
175 Some(cap) => { 121 Some(cap) => {
@@ -193,14 +139,6 @@ fn add_missing_impl_members_inner(
193 }) 139 })
194} 140}
195 141
196fn add_body(fn_def: ast::Fn) -> ast::Fn {
197 if fn_def.body().is_some() {
198 return fn_def;
199 }
200 let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
201 fn_def.with_body(body)
202}
203
204#[cfg(test)] 142#[cfg(test)]
205mod tests { 143mod tests {
206 use crate::tests::{check_assist, check_assist_not_applicable}; 144 use crate::tests::{check_assist, check_assist_not_applicable};
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 37dd61266..d665837a2 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -98,7 +98,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
98 98
99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
100 let group = import_group_message(import_assets.import_candidate()); 100 let group = import_group_message(import_assets.import_candidate());
101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; 101 let scope =
102 ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
102 for (import, _) in proposed_imports { 103 for (import, _) in proposed_imports {
103 acc.add_group( 104 acc.add_group(
104 &group, 105 &group,
diff --git a/crates/assists/src/handlers/change_return_type_to_result.rs b/crates/assists/src/handlers/change_return_type_to_result.rs
deleted file mode 100644
index 76f33a5b6..000000000
--- a/crates/assists/src/handlers/change_return_type_to_result.rs
+++ /dev/null
@@ -1,1091 +0,0 @@
1use std::iter;
2
3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 match_ast, AstNode, SyntaxNode,
6};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: change_return_type_to_result
12//
13// Change the function's return type to Result.
14//
15// ```
16// fn foo() -> i32<|> { 42i32 }
17// ```
18// ->
19// ```
20// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
21// ```
22pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 let parent = ret_type.syntax().parent()?;
25 let block_expr = match_ast! {
26 match parent {
27 ast::Fn(func) => func.body()?,
28 ast::ClosureExpr(closure) => match closure.body()? {
29 Expr::BlockExpr(block) => block,
30 // closures require a block when a return type is specified
31 _ => return None,
32 },
33 _ => return None,
34 }
35 };
36
37 let type_ref = &ret_type.ty()?;
38 let ret_type_str = type_ref.syntax().text().to_string();
39 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
40 if let Some(ret_type_first_part) = first_part_ret_type {
41 if ret_type_first_part.ends_with("Result") {
42 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
43 return None;
44 }
45 }
46
47 acc.add(
48 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
49 "Wrap return type in Result",
50 type_ref.syntax().text_range(),
51 |builder| {
52 let mut tail_return_expr_collector = TailReturnCollector::new();
53 tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
54 tail_return_expr_collector.collect_tail_exprs(&block_expr);
55
56 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
57 let ok_wrapped = make::expr_call(
58 make::expr_path(make::path_unqualified(make::path_segment(make::name_ref(
59 "Ok",
60 )))),
61 make::arg_list(iter::once(ret_expr_arg.clone())),
62 );
63 builder.replace_ast(ret_expr_arg, ok_wrapped);
64 }
65
66 match ctx.config.snippet_cap {
67 Some(cap) => {
68 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
69 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
70 }
71 None => builder
72 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
73 }
74 },
75 )
76}
77
78struct TailReturnCollector {
79 exprs_to_wrap: Vec<ast::Expr>,
80}
81
82impl TailReturnCollector {
83 fn new() -> Self {
84 Self { exprs_to_wrap: vec![] }
85 }
86 /// Collect all`return` expression
87 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
88 let statements = block_expr.statements();
89 for stmt in statements {
90 let expr = match &stmt {
91 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
92 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
93 ast::Stmt::Item(_) => continue,
94 };
95 if let Some(expr) = &expr {
96 self.handle_exprs(expr, collect_break);
97 }
98 }
99
100 // Browse tail expressions for each block
101 if let Some(expr) = block_expr.expr() {
102 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
103 for last_expr in last_exprs {
104 let last_expr = match last_expr {
105 NodeType::Node(expr) => expr,
106 NodeType::Leaf(expr) => expr.syntax().clone(),
107 };
108
109 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
110 self.handle_exprs(&last_expr, collect_break);
111 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
112 let expr_stmt = match &expr_stmt {
113 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
114 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
115 ast::Stmt::Item(_) => None,
116 };
117 if let Some(expr) = &expr_stmt {
118 self.handle_exprs(expr, collect_break);
119 }
120 }
121 }
122 }
123 }
124 }
125
126 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
127 match expr {
128 Expr::BlockExpr(block_expr) => {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 Expr::ReturnExpr(ret_expr) => {
132 if let Some(ret_expr_arg) = &ret_expr.expr() {
133 self.exprs_to_wrap.push(ret_expr_arg.clone());
134 }
135 }
136 Expr::BreakExpr(break_expr) if collect_break => {
137 if let Some(break_expr_arg) = &break_expr.expr() {
138 self.exprs_to_wrap.push(break_expr_arg.clone());
139 }
140 }
141 Expr::IfExpr(if_expr) => {
142 for block in if_expr.blocks() {
143 self.collect_jump_exprs(&block, collect_break);
144 }
145 }
146 Expr::LoopExpr(loop_expr) => {
147 if let Some(block_expr) = loop_expr.loop_body() {
148 self.collect_jump_exprs(&block_expr, collect_break);
149 }
150 }
151 Expr::ForExpr(for_expr) => {
152 if let Some(block_expr) = for_expr.loop_body() {
153 self.collect_jump_exprs(&block_expr, collect_break);
154 }
155 }
156 Expr::WhileExpr(while_expr) => {
157 if let Some(block_expr) = while_expr.loop_body() {
158 self.collect_jump_exprs(&block_expr, collect_break);
159 }
160 }
161 Expr::MatchExpr(match_expr) => {
162 if let Some(arm_list) = match_expr.match_arm_list() {
163 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
164 self.handle_exprs(&expr, collect_break);
165 });
166 }
167 }
168 _ => {}
169 }
170 }
171
172 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
173 if let Some(expr) = block.expr() {
174 self.handle_exprs(&expr, true);
175 self.fetch_tail_exprs(&expr);
176 }
177 }
178
179 fn fetch_tail_exprs(&mut self, expr: &Expr) {
180 if let Some(exprs) = get_tail_expr_from_block(expr) {
181 for node_type in &exprs {
182 match node_type {
183 NodeType::Leaf(expr) => {
184 self.exprs_to_wrap.push(expr.clone());
185 }
186 NodeType::Node(expr) => {
187 if let Some(last_expr) = Expr::cast(expr.clone()) {
188 self.fetch_tail_exprs(&last_expr);
189 }
190 }
191 }
192 }
193 }
194 }
195}
196
197#[derive(Debug)]
198enum NodeType {
199 Leaf(ast::Expr),
200 Node(SyntaxNode),
201}
202
203/// Get a tail expression inside a block
204fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
205 match expr {
206 Expr::IfExpr(if_expr) => {
207 let mut nodes = vec![];
208 for block in if_expr.blocks() {
209 if let Some(block_expr) = block.expr() {
210 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
211 nodes.extend(tail_exprs);
212 }
213 } else if let Some(last_expr) = block.syntax().last_child() {
214 nodes.push(NodeType::Node(last_expr));
215 } else {
216 nodes.push(NodeType::Node(block.syntax().clone()));
217 }
218 }
219 Some(nodes)
220 }
221 Expr::LoopExpr(loop_expr) => {
222 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
223 }
224 Expr::ForExpr(for_expr) => {
225 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
226 }
227 Expr::WhileExpr(while_expr) => {
228 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
229 }
230 Expr::BlockExpr(block_expr) => {
231 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
232 }
233 Expr::MatchExpr(match_expr) => {
234 let arm_list = match_expr.match_arm_list()?;
235 let arms: Vec<NodeType> = arm_list
236 .arms()
237 .filter_map(|match_arm| match_arm.expr())
238 .map(|expr| match expr {
239 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
240 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
241 _ => match expr.syntax().last_child() {
242 Some(last_expr) => NodeType::Node(last_expr),
243 None => NodeType::Node(expr.syntax().clone()),
244 },
245 })
246 .collect();
247
248 Some(arms)
249 }
250 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e)]),
251 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
252
253 Expr::CallExpr(_)
254 | Expr::Literal(_)
255 | Expr::TupleExpr(_)
256 | Expr::ArrayExpr(_)
257 | Expr::ParenExpr(_)
258 | Expr::PathExpr(_)
259 | Expr::RecordExpr(_)
260 | Expr::IndexExpr(_)
261 | Expr::MethodCallExpr(_)
262 | Expr::AwaitExpr(_)
263 | Expr::CastExpr(_)
264 | Expr::RefExpr(_)
265 | Expr::PrefixExpr(_)
266 | Expr::RangeExpr(_)
267 | Expr::BinExpr(_)
268 | Expr::MacroCall(_)
269 | Expr::BoxExpr(_) => Some(vec![NodeType::Leaf(expr.clone())]),
270 _ => None,
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::tests::{check_assist, check_assist_not_applicable};
277
278 use super::*;
279
280 #[test]
281 fn change_return_type_to_result_simple() {
282 check_assist(
283 change_return_type_to_result,
284 r#"fn foo() -> i3<|>2 {
285 let test = "test";
286 return 42i32;
287 }"#,
288 r#"fn foo() -> Result<i32, ${0:_}> {
289 let test = "test";
290 return Ok(42i32);
291 }"#,
292 );
293 }
294
295 #[test]
296 fn change_return_type_to_result_simple_closure() {
297 check_assist(
298 change_return_type_to_result,
299 r#"fn foo() {
300 || -> i32<|> {
301 let test = "test";
302 return 42i32;
303 };
304 }"#,
305 r#"fn foo() {
306 || -> Result<i32, ${0:_}> {
307 let test = "test";
308 return Ok(42i32);
309 };
310 }"#,
311 );
312 }
313
314 #[test]
315 fn change_return_type_to_result_simple_return_type_bad_cursor() {
316 check_assist_not_applicable(
317 change_return_type_to_result,
318 r#"fn foo() -> i32 {
319 let test = "test";<|>
320 return 42i32;
321 }"#,
322 );
323 }
324
325 #[test]
326 fn change_return_type_to_result_simple_return_type_bad_cursor_closure() {
327 check_assist_not_applicable(
328 change_return_type_to_result,
329 r#"fn foo() {
330 || -> i32 {
331 let test = "test";<|>
332 return 42i32;
333 };
334 }"#,
335 );
336 }
337
338 #[test]
339 fn change_return_type_to_result_closure_non_block() {
340 check_assist_not_applicable(
341 change_return_type_to_result,
342 r#"fn foo() {
343 || -> i<|>32 3;
344 }"#,
345 );
346 }
347
348 #[test]
349 fn change_return_type_to_result_simple_return_type_already_result_std() {
350 check_assist_not_applicable(
351 change_return_type_to_result,
352 r#"fn foo() -> std::result::Result<i32<|>, String> {
353 let test = "test";
354 return 42i32;
355 }"#,
356 );
357 }
358
359 #[test]
360 fn change_return_type_to_result_simple_return_type_already_result() {
361 mark::check!(change_return_type_to_result_simple_return_type_already_result);
362 check_assist_not_applicable(
363 change_return_type_to_result,
364 r#"fn foo() -> Result<i32<|>, String> {
365 let test = "test";
366 return 42i32;
367 }"#,
368 );
369 }
370
371 #[test]
372 fn change_return_type_to_result_simple_return_type_already_result_closure() {
373 check_assist_not_applicable(
374 change_return_type_to_result,
375 r#"fn foo() {
376 || -> Result<i32<|>, String> {
377 let test = "test";
378 return 42i32;
379 };
380 }"#,
381 );
382 }
383
384 #[test]
385 fn change_return_type_to_result_simple_with_cursor() {
386 check_assist(
387 change_return_type_to_result,
388 r#"fn foo() -> <|>i32 {
389 let test = "test";
390 return 42i32;
391 }"#,
392 r#"fn foo() -> Result<i32, ${0:_}> {
393 let test = "test";
394 return Ok(42i32);
395 }"#,
396 );
397 }
398
399 #[test]
400 fn change_return_type_to_result_simple_with_tail() {
401 check_assist(
402 change_return_type_to_result,
403 r#"fn foo() -><|> i32 {
404 let test = "test";
405 42i32
406 }"#,
407 r#"fn foo() -> Result<i32, ${0:_}> {
408 let test = "test";
409 Ok(42i32)
410 }"#,
411 );
412 }
413
414 #[test]
415 fn change_return_type_to_result_simple_with_tail_closure() {
416 check_assist(
417 change_return_type_to_result,
418 r#"fn foo() {
419 || -><|> i32 {
420 let test = "test";
421 42i32
422 };
423 }"#,
424 r#"fn foo() {
425 || -> Result<i32, ${0:_}> {
426 let test = "test";
427 Ok(42i32)
428 };
429 }"#,
430 );
431 }
432
433 #[test]
434 fn change_return_type_to_result_simple_with_tail_only() {
435 check_assist(
436 change_return_type_to_result,
437 r#"fn foo() -> i32<|> {
438 42i32
439 }"#,
440 r#"fn foo() -> Result<i32, ${0:_}> {
441 Ok(42i32)
442 }"#,
443 );
444 }
445
446 #[test]
447 fn change_return_type_to_result_simple_with_tail_block_like() {
448 check_assist(
449 change_return_type_to_result,
450 r#"fn foo() -> i32<|> {
451 if true {
452 42i32
453 } else {
454 24i32
455 }
456 }"#,
457 r#"fn foo() -> Result<i32, ${0:_}> {
458 if true {
459 Ok(42i32)
460 } else {
461 Ok(24i32)
462 }
463 }"#,
464 );
465 }
466
467 #[test]
468 fn change_return_type_to_result_simple_without_block_closure() {
469 check_assist(
470 change_return_type_to_result,
471 r#"fn foo() {
472 || -> i32<|> {
473 if true {
474 42i32
475 } else {
476 24i32
477 }
478 };
479 }"#,
480 r#"fn foo() {
481 || -> Result<i32, ${0:_}> {
482 if true {
483 Ok(42i32)
484 } else {
485 Ok(24i32)
486 }
487 };
488 }"#,
489 );
490 }
491
492 #[test]
493 fn change_return_type_to_result_simple_with_nested_if() {
494 check_assist(
495 change_return_type_to_result,
496 r#"fn foo() -> i32<|> {
497 if true {
498 if false {
499 1
500 } else {
501 2
502 }
503 } else {
504 24i32
505 }
506 }"#,
507 r#"fn foo() -> Result<i32, ${0:_}> {
508 if true {
509 if false {
510 Ok(1)
511 } else {
512 Ok(2)
513 }
514 } else {
515 Ok(24i32)
516 }
517 }"#,
518 );
519 }
520
521 #[test]
522 fn change_return_type_to_result_simple_with_await() {
523 check_assist(
524 change_return_type_to_result,
525 r#"async fn foo() -> i<|>32 {
526 if true {
527 if false {
528 1.await
529 } else {
530 2.await
531 }
532 } else {
533 24i32.await
534 }
535 }"#,
536 r#"async fn foo() -> Result<i32, ${0:_}> {
537 if true {
538 if false {
539 Ok(1.await)
540 } else {
541 Ok(2.await)
542 }
543 } else {
544 Ok(24i32.await)
545 }
546 }"#,
547 );
548 }
549
550 #[test]
551 fn change_return_type_to_result_simple_with_array() {
552 check_assist(
553 change_return_type_to_result,
554 r#"fn foo() -> [i32;<|> 3] {
555 [1, 2, 3]
556 }"#,
557 r#"fn foo() -> Result<[i32; 3], ${0:_}> {
558 Ok([1, 2, 3])
559 }"#,
560 );
561 }
562
563 #[test]
564 fn change_return_type_to_result_simple_with_cast() {
565 check_assist(
566 change_return_type_to_result,
567 r#"fn foo() -<|>> i32 {
568 if true {
569 if false {
570 1 as i32
571 } else {
572 2 as i32
573 }
574 } else {
575 24 as i32
576 }
577 }"#,
578 r#"fn foo() -> Result<i32, ${0:_}> {
579 if true {
580 if false {
581 Ok(1 as i32)
582 } else {
583 Ok(2 as i32)
584 }
585 } else {
586 Ok(24 as i32)
587 }
588 }"#,
589 );
590 }
591
592 #[test]
593 fn change_return_type_to_result_simple_with_tail_block_like_match() {
594 check_assist(
595 change_return_type_to_result,
596 r#"fn foo() -> i32<|> {
597 let my_var = 5;
598 match my_var {
599 5 => 42i32,
600 _ => 24i32,
601 }
602 }"#,
603 r#"fn foo() -> Result<i32, ${0:_}> {
604 let my_var = 5;
605 match my_var {
606 5 => Ok(42i32),
607 _ => Ok(24i32),
608 }
609 }"#,
610 );
611 }
612
613 #[test]
614 fn change_return_type_to_result_simple_with_loop_with_tail() {
615 check_assist(
616 change_return_type_to_result,
617 r#"fn foo() -> i32<|> {
618 let my_var = 5;
619 loop {
620 println!("test");
621 5
622 }
623
624 my_var
625 }"#,
626 r#"fn foo() -> Result<i32, ${0:_}> {
627 let my_var = 5;
628 loop {
629 println!("test");
630 5
631 }
632
633 Ok(my_var)
634 }"#,
635 );
636 }
637
638 #[test]
639 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
640 check_assist(
641 change_return_type_to_result,
642 r#"fn foo() -> i32<|> {
643 let my_var = let x = loop {
644 break 1;
645 };
646
647 my_var
648 }"#,
649 r#"fn foo() -> Result<i32, ${0:_}> {
650 let my_var = let x = loop {
651 break 1;
652 };
653
654 Ok(my_var)
655 }"#,
656 );
657 }
658
659 #[test]
660 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
661 check_assist(
662 change_return_type_to_result,
663 r#"fn foo() -> i32<|> {
664 let my_var = 5;
665 let res = match my_var {
666 5 => 42i32,
667 _ => return 24i32,
668 };
669
670 res
671 }"#,
672 r#"fn foo() -> Result<i32, ${0:_}> {
673 let my_var = 5;
674 let res = match my_var {
675 5 => 42i32,
676 _ => return Ok(24i32),
677 };
678
679 Ok(res)
680 }"#,
681 );
682
683 check_assist(
684 change_return_type_to_result,
685 r#"fn foo() -> i32<|> {
686 let my_var = 5;
687 let res = if my_var == 5 {
688 42i32
689 } else {
690 return 24i32;
691 };
692
693 res
694 }"#,
695 r#"fn foo() -> Result<i32, ${0:_}> {
696 let my_var = 5;
697 let res = if my_var == 5 {
698 42i32
699 } else {
700 return Ok(24i32);
701 };
702
703 Ok(res)
704 }"#,
705 );
706 }
707
708 #[test]
709 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
710 check_assist(
711 change_return_type_to_result,
712 r#"fn foo() -> i32<|> {
713 let my_var = 5;
714 match my_var {
715 5 => {
716 if true {
717 42i32
718 } else {
719 25i32
720 }
721 },
722 _ => {
723 let test = "test";
724 if test == "test" {
725 return bar();
726 }
727 53i32
728 },
729 }
730 }"#,
731 r#"fn foo() -> Result<i32, ${0:_}> {
732 let my_var = 5;
733 match my_var {
734 5 => {
735 if true {
736 Ok(42i32)
737 } else {
738 Ok(25i32)
739 }
740 },
741 _ => {
742 let test = "test";
743 if test == "test" {
744 return Ok(bar());
745 }
746 Ok(53i32)
747 },
748 }
749 }"#,
750 );
751 }
752
753 #[test]
754 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
755 check_assist(
756 change_return_type_to_result,
757 r#"fn foo() -> i<|>32 {
758 let test = "test";
759 if test == "test" {
760 return 24i32;
761 }
762 53i32
763 }"#,
764 r#"fn foo() -> Result<i32, ${0:_}> {
765 let test = "test";
766 if test == "test" {
767 return Ok(24i32);
768 }
769 Ok(53i32)
770 }"#,
771 );
772 }
773
774 #[test]
775 fn change_return_type_to_result_simple_with_closure() {
776 check_assist(
777 change_return_type_to_result,
778 r#"fn foo(the_field: u32) -><|> u32 {
779 let true_closure = || {
780 return true;
781 };
782 if the_field < 5 {
783 let mut i = 0;
784
785
786 if true_closure() {
787 return 99;
788 } else {
789 return 0;
790 }
791 }
792
793 the_field
794 }"#,
795 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
796 let true_closure = || {
797 return true;
798 };
799 if the_field < 5 {
800 let mut i = 0;
801
802
803 if true_closure() {
804 return Ok(99);
805 } else {
806 return Ok(0);
807 }
808 }
809
810 Ok(the_field)
811 }"#,
812 );
813
814 check_assist(
815 change_return_type_to_result,
816 r#"fn foo(the_field: u32) -> u32<|> {
817 let true_closure = || {
818 return true;
819 };
820 if the_field < 5 {
821 let mut i = 0;
822
823
824 if true_closure() {
825 return 99;
826 } else {
827 return 0;
828 }
829 }
830 let t = None;
831
832 t.unwrap_or_else(|| the_field)
833 }"#,
834 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
835 let true_closure = || {
836 return true;
837 };
838 if the_field < 5 {
839 let mut i = 0;
840
841
842 if true_closure() {
843 return Ok(99);
844 } else {
845 return Ok(0);
846 }
847 }
848 let t = None;
849
850 Ok(t.unwrap_or_else(|| the_field))
851 }"#,
852 );
853 }
854
855 #[test]
856 fn change_return_type_to_result_simple_with_weird_forms() {
857 check_assist(
858 change_return_type_to_result,
859 r#"fn foo() -> i32<|> {
860 let test = "test";
861 if test == "test" {
862 return 24i32;
863 }
864 let mut i = 0;
865 loop {
866 if i == 1 {
867 break 55;
868 }
869 i += 1;
870 }
871 }"#,
872 r#"fn foo() -> Result<i32, ${0:_}> {
873 let test = "test";
874 if test == "test" {
875 return Ok(24i32);
876 }
877 let mut i = 0;
878 loop {
879 if i == 1 {
880 break Ok(55);
881 }
882 i += 1;
883 }
884 }"#,
885 );
886
887 check_assist(
888 change_return_type_to_result,
889 r#"fn foo() -> i32<|> {
890 let test = "test";
891 if test == "test" {
892 return 24i32;
893 }
894 let mut i = 0;
895 loop {
896 loop {
897 if i == 1 {
898 break 55;
899 }
900 i += 1;
901 }
902 }
903 }"#,
904 r#"fn foo() -> Result<i32, ${0:_}> {
905 let test = "test";
906 if test == "test" {
907 return Ok(24i32);
908 }
909 let mut i = 0;
910 loop {
911 loop {
912 if i == 1 {
913 break Ok(55);
914 }
915 i += 1;
916 }
917 }
918 }"#,
919 );
920
921 check_assist(
922 change_return_type_to_result,
923 r#"fn foo() -> i3<|>2 {
924 let test = "test";
925 let other = 5;
926 if test == "test" {
927 let res = match other {
928 5 => 43,
929 _ => return 56,
930 };
931 }
932 let mut i = 0;
933 loop {
934 loop {
935 if i == 1 {
936 break 55;
937 }
938 i += 1;
939 }
940 }
941 }"#,
942 r#"fn foo() -> Result<i32, ${0:_}> {
943 let test = "test";
944 let other = 5;
945 if test == "test" {
946 let res = match other {
947 5 => 43,
948 _ => return Ok(56),
949 };
950 }
951 let mut i = 0;
952 loop {
953 loop {
954 if i == 1 {
955 break Ok(55);
956 }
957 i += 1;
958 }
959 }
960 }"#,
961 );
962
963 check_assist(
964 change_return_type_to_result,
965 r#"fn foo(the_field: u32) -> u32<|> {
966 if the_field < 5 {
967 let mut i = 0;
968 loop {
969 if i > 5 {
970 return 55u32;
971 }
972 i += 3;
973 }
974
975 match i {
976 5 => return 99,
977 _ => return 0,
978 };
979 }
980
981 the_field
982 }"#,
983 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
984 if the_field < 5 {
985 let mut i = 0;
986 loop {
987 if i > 5 {
988 return Ok(55u32);
989 }
990 i += 3;
991 }
992
993 match i {
994 5 => return Ok(99),
995 _ => return Ok(0),
996 };
997 }
998
999 Ok(the_field)
1000 }"#,
1001 );
1002
1003 check_assist(
1004 change_return_type_to_result,
1005 r#"fn foo(the_field: u32) -> u3<|>2 {
1006 if the_field < 5 {
1007 let mut i = 0;
1008
1009 match i {
1010 5 => return 99,
1011 _ => return 0,
1012 }
1013 }
1014
1015 the_field
1016 }"#,
1017 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1018 if the_field < 5 {
1019 let mut i = 0;
1020
1021 match i {
1022 5 => return Ok(99),
1023 _ => return Ok(0),
1024 }
1025 }
1026
1027 Ok(the_field)
1028 }"#,
1029 );
1030
1031 check_assist(
1032 change_return_type_to_result,
1033 r#"fn foo(the_field: u32) -> u32<|> {
1034 if the_field < 5 {
1035 let mut i = 0;
1036
1037 if i == 5 {
1038 return 99
1039 } else {
1040 return 0
1041 }
1042 }
1043
1044 the_field
1045 }"#,
1046 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1047 if the_field < 5 {
1048 let mut i = 0;
1049
1050 if i == 5 {
1051 return Ok(99)
1052 } else {
1053 return Ok(0)
1054 }
1055 }
1056
1057 Ok(the_field)
1058 }"#,
1059 );
1060
1061 check_assist(
1062 change_return_type_to_result,
1063 r#"fn foo(the_field: u32) -> <|>u32 {
1064 if the_field < 5 {
1065 let mut i = 0;
1066
1067 if i == 5 {
1068 return 99;
1069 } else {
1070 return 0;
1071 }
1072 }
1073
1074 the_field
1075 }"#,
1076 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1077 if the_field < 5 {
1078 let mut i = 0;
1079
1080 if i == 5 {
1081 return Ok(99);
1082 } else {
1083 return Ok(0);
1084 }
1085 }
1086
1087 Ok(the_field)
1088 }"#,
1089 );
1090 }
1091}
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 853266395..f51a9a4ad 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -5,13 +5,13 @@ use ide_db::{
5 search::SearchScope, 5 search::SearchScope,
6}; 6};
7use syntax::{ 7use syntax::{
8 algo, 8 algo::SyntaxRewriter,
9 ast::{self, make}, 9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T, 10 AstNode, Direction, SyntaxNode, SyntaxToken, T,
11}; 11};
12 12
13use crate::{ 13use crate::{
14 assist_context::{AssistBuilder, AssistContext, Assists}, 14 assist_context::{AssistContext, Assists},
15 AssistId, AssistKind, 15 AssistId, AssistKind,
16}; 16};
17 17
@@ -61,7 +61,9 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
61 "Expand glob import", 61 "Expand glob import",
62 target.text_range(), 62 target.text_range(),
63 |builder| { 63 |builder| {
64 replace_ast(builder, parent, mod_path, names_to_import); 64 let mut rewriter = SyntaxRewriter::default();
65 replace_ast(&mut rewriter, parent, mod_path, names_to_import);
66 builder.rewrite(rewriter);
65 }, 67 },
66 ) 68 )
67} 69}
@@ -236,7 +238,7 @@ fn find_names_to_import(
236} 238}
237 239
238fn replace_ast( 240fn replace_ast(
239 builder: &mut AssistBuilder, 241 rewriter: &mut SyntaxRewriter,
240 parent: Either<ast::UseTree, ast::UseTreeList>, 242 parent: Either<ast::UseTree, ast::UseTreeList>,
241 path: ast::Path, 243 path: ast::Path,
242 names_to_import: Vec<Name>, 244 names_to_import: Vec<Name>,
@@ -264,32 +266,21 @@ fn replace_ast(
264 match use_trees.as_slice() { 266 match use_trees.as_slice() {
265 [name] => { 267 [name] => {
266 if let Some(end_path) = name.path() { 268 if let Some(end_path) = name.path() {
267 let replacement = 269 rewriter.replace_ast(
268 make::use_tree(make::path_concat(path, end_path), None, None, false); 270 &parent.left_or_else(|tl| tl.parent_use_tree()),
269 271 &make::use_tree(make::path_concat(path, end_path), None, None, false),
270 algo::diff( 272 );
271 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
272 replacement.syntax(),
273 )
274 .into_text_edit(builder.text_edit_builder());
275 } 273 }
276 } 274 }
277 names => { 275 names => match &parent {
278 let replacement = match parent { 276 Either::Left(parent) => rewriter.replace_ast(
279 Either::Left(_) => { 277 parent,
280 make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false) 278 &make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false),
281 .syntax() 279 ),
282 .clone() 280 Either::Right(parent) => {
283 } 281 rewriter.replace_ast(parent, &make::use_tree_list(names.to_owned()))
284 Either::Right(_) => make::use_tree_list(names.to_owned()).syntax().clone(), 282 }
285 }; 283 },
286
287 algo::diff(
288 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
289 &replacement,
290 )
291 .into_text_edit(builder.text_edit_builder());
292 }
293 }; 284 };
294} 285}
295 286
@@ -884,4 +875,33 @@ fn qux(baz: Baz) {}
884 ", 875 ",
885 ) 876 )
886 } 877 }
878
879 #[test]
880 fn expanding_glob_import_single_nested_glob_only() {
881 check_assist(
882 expand_glob_import,
883 r"
884mod foo {
885 pub struct Bar;
886}
887
888use foo::{*<|>};
889
890struct Baz {
891 bar: Bar
892}
893",
894 r"
895mod foo {
896 pub struct Bar;
897}
898
899use foo::Bar;
900
901struct Baz {
902 bar: Bar
903}
904",
905 );
906 }
887} 907}
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 14209b771..cac77c49b 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -5,10 +5,9 @@ use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
5use ide_db::{defs::Definition, search::Reference, RootDatabase}; 5use ide_db::{defs::Definition, search::Reference, RootDatabase};
6use rustc_hash::{FxHashMap, FxHashSet}; 6use rustc_hash::{FxHashMap, FxHashSet};
7use syntax::{ 7use syntax::{
8 algo::find_node_at_offset, 8 algo::{find_node_at_offset, SyntaxRewriter},
9 algo::SyntaxRewriter, 9 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
10 ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, 10 SourceFile, SyntaxElement, SyntaxNode, T,
11 SourceFile, SyntaxElement,
12}; 11};
13 12
14use crate::{ 13use crate::{
@@ -130,18 +129,21 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &En
130fn insert_import( 129fn insert_import(
131 ctx: &AssistContext, 130 ctx: &AssistContext,
132 rewriter: &mut SyntaxRewriter, 131 rewriter: &mut SyntaxRewriter,
133 path: &ast::PathExpr, 132 scope_node: &SyntaxNode,
134 module: &Module, 133 module: &Module,
135 enum_module_def: &ModuleDef, 134 enum_module_def: &ModuleDef,
136 variant_hir_name: &Name, 135 variant_hir_name: &Name,
137) -> Option<()> { 136) -> Option<()> {
138 let db = ctx.db(); 137 let db = ctx.db();
139 let mod_path = module.find_use_path(db, enum_module_def.clone()); 138 let mod_path = module.find_use_path_prefixed(
139 db,
140 enum_module_def.clone(),
141 ctx.config.insert_use.prefix_kind,
142 );
140 if let Some(mut mod_path) = mod_path { 143 if let Some(mut mod_path) = mod_path {
141 mod_path.segments.pop(); 144 mod_path.segments.pop();
142 mod_path.segments.push(variant_hir_name.clone()); 145 mod_path.segments.push(variant_hir_name.clone());
143 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 146 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
144
145 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge); 147 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
146 } 148 }
147 Some(()) 149 Some(())
@@ -204,27 +206,31 @@ fn update_reference(
204 variant_hir_name: &Name, 206 variant_hir_name: &Name,
205 visited_modules_set: &mut FxHashSet<Module>, 207 visited_modules_set: &mut FxHashSet<Module>,
206) -> Option<()> { 208) -> Option<()> {
207 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 209 let offset = reference.file_range.range.start();
208 source_file.syntax(), 210 let (segment, expr) = if let Some(path_expr) =
209 reference.file_range.range.start(), 211 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
210 )?; 212 {
211 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 213 // tuple variant
212 let list = call.arg_list()?; 214 (path_expr.path()?.segment()?, path_expr.syntax().parent()?.clone())
213 let segment = path_expr.path()?.segment()?; 215 } else if let Some(record_expr) =
214 let module = ctx.sema.scope(&path_expr.syntax()).module()?; 216 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
217 {
218 // record variant
219 (record_expr.path()?.segment()?, record_expr.syntax().clone())
220 } else {
221 return None;
222 };
223
224 let module = ctx.sema.scope(&expr).module()?;
215 if !visited_modules_set.contains(&module) { 225 if !visited_modules_set.contains(&module) {
216 if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name) 226 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
217 .is_some()
218 { 227 {
219 visited_modules_set.insert(module); 228 visited_modules_set.insert(module);
220 } 229 }
221 } 230 }
222 231 rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
223 let lparen = syntax::SyntaxElement::from(list.l_paren_token()?); 232 rewriter.insert_after(segment.syntax(), segment.syntax());
224 let rparen = syntax::SyntaxElement::from(list.r_paren_token()?); 233 rewriter.insert_after(&expr, &make::token(T![')']));
225 rewriter.insert_after(&lparen, segment.syntax());
226 rewriter.insert_after(&lparen, &lparen);
227 rewriter.insert_before(&rparen, &rparen);
228 Some(()) 234 Some(())
229} 235}
230 236
@@ -320,7 +326,7 @@ fn another_fn() {
320 r#"use my_mod::my_other_mod::MyField; 326 r#"use my_mod::my_other_mod::MyField;
321 327
322mod my_mod { 328mod my_mod {
323 use my_other_mod::MyField; 329 use self::my_other_mod::MyField;
324 330
325 fn another_fn() { 331 fn another_fn() {
326 let m = my_other_mod::MyEnum::MyField(MyField(1, 1)); 332 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
@@ -345,6 +351,130 @@ fn another_fn() {
345 ); 351 );
346 } 352 }
347 353
354 #[test]
355 fn extract_record_fix_references() {
356 check_assist(
357 extract_struct_from_enum_variant,
358 r#"
359enum E {
360 <|>V { i: i32, j: i32 }
361}
362
363fn f() {
364 let e = E::V { i: 9, j: 2 };
365}
366"#,
367 r#"
368struct V{ pub i: i32, pub j: i32 }
369
370enum E {
371 V(V)
372}
373
374fn f() {
375 let e = E::V(V { i: 9, j: 2 });
376}
377"#,
378 )
379 }
380
381 #[test]
382 fn test_several_files() {
383 check_assist(
384 extract_struct_from_enum_variant,
385 r#"
386//- /main.rs
387enum E {
388 <|>V(i32, i32)
389}
390mod foo;
391
392//- /foo.rs
393use crate::E;
394fn f() {
395 let e = E::V(9, 2);
396}
397"#,
398 r#"
399//- /main.rs
400struct V(pub i32, pub i32);
401
402enum E {
403 V(V)
404}
405mod foo;
406
407//- /foo.rs
408use crate::{E, V};
409fn f() {
410 let e = E::V(V(9, 2));
411}
412"#,
413 )
414 }
415
416 #[test]
417 fn test_several_files_record() {
418 check_assist(
419 extract_struct_from_enum_variant,
420 r#"
421//- /main.rs
422enum E {
423 <|>V { i: i32, j: i32 }
424}
425mod foo;
426
427//- /foo.rs
428use crate::E;
429fn f() {
430 let e = E::V { i: 9, j: 2 };
431}
432"#,
433 r#"
434//- /main.rs
435struct V{ pub i: i32, pub j: i32 }
436
437enum E {
438 V(V)
439}
440mod foo;
441
442//- /foo.rs
443use crate::{E, V};
444fn f() {
445 let e = E::V(V { i: 9, j: 2 });
446}
447"#,
448 )
449 }
450
451 #[test]
452 fn test_extract_struct_record_nested_call_exp() {
453 check_assist(
454 extract_struct_from_enum_variant,
455 r#"
456enum A { <|>One { a: u32, b: u32 } }
457
458struct B(A);
459
460fn foo() {
461 let _ = B(A::One { a: 1, b: 2 });
462}
463"#,
464 r#"
465struct One{ pub a: u32, pub b: u32 }
466
467enum A { One(One) }
468
469struct B(A);
470
471fn foo() {
472 let _ = B(A::One(One { a: 1, b: 2 }));
473}
474"#,
475 );
476 }
477
348 fn check_not_applicable(ra_fixture: &str) { 478 fn check_not_applicable(ra_fixture: &str) {
349 let fixture = 479 let fixture =
350 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); 480 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
diff --git a/crates/assists/src/handlers/ignore_test.rs b/crates/assists/src/handlers/ignore_test.rs
new file mode 100644
index 000000000..5096a0005
--- /dev/null
+++ b/crates/assists/src/handlers/ignore_test.rs
@@ -0,0 +1,103 @@
1use syntax::{
2 ast::{self, AttrsOwner},
3 AstNode, AstToken,
4};
5
6use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: ignore_test
9//
10// Adds `#[ignore]` attribute to the test.
11//
12// ```
13// <|>#[test]
14// fn arithmetics {
15// assert_eq!(2 + 2, 5);
16// }
17// ```
18// ->
19// ```
20// #[test]
21// #[ignore]
22// fn arithmetics {
23// assert_eq!(2 + 2, 5);
24// }
25// ```
26pub(crate) fn ignore_test(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let attr: ast::Attr = ctx.find_node_at_offset()?;
28 let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
29 let attr = test_related_attribute(&func)?;
30
31 match has_ignore_attribute(&func) {
32 None => acc.add(
33 AssistId("ignore_test", AssistKind::None),
34 "Ignore this test",
35 attr.syntax().text_range(),
36 |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")),
37 ),
38 Some(ignore_attr) => acc.add(
39 AssistId("unignore_test", AssistKind::None),
40 "Re-enable this test",
41 ignore_attr.syntax().text_range(),
42 |builder| {
43 builder.delete(ignore_attr.syntax().text_range());
44 let whitespace = ignore_attr
45 .syntax()
46 .next_sibling_or_token()
47 .and_then(|x| x.into_token())
48 .and_then(ast::Whitespace::cast);
49 if let Some(whitespace) = whitespace {
50 builder.delete(whitespace.syntax().text_range());
51 }
52 },
53 ),
54 }
55}
56
57fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
58 fn_def.attrs().find_map(|attr| {
59 if attr.path()?.syntax().text() == "ignore" {
60 Some(attr)
61 } else {
62 None
63 }
64 })
65}
66
67#[cfg(test)]
68mod tests {
69 use super::ignore_test;
70 use crate::tests::check_assist;
71
72 #[test]
73 fn test_base_case() {
74 check_assist(
75 ignore_test,
76 r#"
77 #[test<|>]
78 fn test() {}
79 "#,
80 r#"
81 #[test]
82 #[ignore]
83 fn test() {}
84 "#,
85 )
86 }
87
88 #[test]
89 fn test_unignore() {
90 check_assist(
91 ignore_test,
92 r#"
93 #[test<|>]
94 #[ignore]
95 fn test() {}
96 "#,
97 r#"
98 #[test]
99 fn test() {}
100 "#,
101 )
102 }
103}
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 9731344b8..eae6367c1 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 SyntaxElement, SyntaxKind, TextRange, TextSize, T, 3 match_ast, SyntaxElement, SyntaxKind, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -49,12 +49,29 @@ fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
49 macro_text_with_brackets.len() - TextSize::of(')'), 49 macro_text_with_brackets.len() - TextSize::of(')'),
50 )); 50 ));
51 51
52 let is_leaf = macro_call.syntax().next_sibling().is_none(); 52 Some(
53 Some(if !is_leaf && needs_parentheses_around_macro_contents(contents) { 53 if !is_leaf_or_control_flow_expr(macro_call)
54 format!("({})", macro_text_in_brackets) 54 && needs_parentheses_around_macro_contents(contents)
55 } else { 55 {
56 macro_text_in_brackets.to_string() 56 format!("({})", macro_text_in_brackets)
57 }) 57 } else {
58 macro_text_in_brackets.to_string()
59 },
60 )
61}
62
63fn is_leaf_or_control_flow_expr(macro_call: &ast::MacroCall) -> bool {
64 macro_call.syntax().next_sibling().is_none()
65 || match macro_call.syntax().parent() {
66 Some(parent) => match_ast! {
67 match parent {
68 ast::Condition(_it) => true,
69 ast::MatchExpr(_it) => true,
70 _ => false,
71 }
72 },
73 None => false,
74 }
58} 75}
59 76
60/// Verifies that the given macro_call actually matches the given name 77/// Verifies that the given macro_call actually matches the given name
@@ -361,4 +378,44 @@ fn main() {
361 r#"let res = (foo..=bar).foo();"#, 378 r#"let res = (foo..=bar).foo();"#,
362 ); 379 );
363 } 380 }
381
382 #[test]
383 fn test_remove_dbg_followed_by_block() {
384 check_assist(
385 remove_dbg,
386 r#"fn foo() {
387 if <|>dbg!(x || y) {}
388}"#,
389 r#"fn foo() {
390 if x || y {}
391}"#,
392 );
393 check_assist(
394 remove_dbg,
395 r#"fn foo() {
396 while let foo = <|>dbg!(&x) {}
397}"#,
398 r#"fn foo() {
399 while let foo = &x {}
400}"#,
401 );
402 check_assist(
403 remove_dbg,
404 r#"fn foo() {
405 if let foo = <|>dbg!(&x) {}
406}"#,
407 r#"fn foo() {
408 if let foo = &x {}
409}"#,
410 );
411 check_assist(
412 remove_dbg,
413 r#"fn foo() {
414 match <|>dbg!(&x) {}
415}"#,
416 r#"fn foo() {
417 match &x {}
418}"#,
419 );
420 }
364} 421}
diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs
index 5fccca54b..1ff5e92b0 100644
--- a/crates/assists/src/handlers/remove_unused_param.rs
+++ b/crates/assists/src/handlers/remove_unused_param.rs
@@ -73,7 +73,8 @@ fn process_usage(
73 let source_file = ctx.sema.parse(usage.file_range.file_id); 73 let source_file = ctx.sema.parse(usage.file_range.file_id);
74 let call_expr: ast::CallExpr = 74 let call_expr: ast::CallExpr =
75 find_node_at_range(source_file.syntax(), usage.file_range.range)?; 75 find_node_at_range(source_file.syntax(), usage.file_range.range)?;
76 if call_expr.expr()?.syntax().text_range() != usage.file_range.range { 76 let call_expr_range = call_expr.expr()?.syntax().text_range();
77 if !call_expr_range.contains_range(usage.file_range.range) {
77 return None; 78 return None;
78 } 79 }
79 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; 80 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
@@ -118,6 +119,53 @@ fn b() { foo(9, ) }
118 } 119 }
119 120
120 #[test] 121 #[test]
122 fn remove_unused_qualified_call() {
123 check_assist(
124 remove_unused_param,
125 r#"
126mod bar { pub fn foo(x: i32, <|>y: i32) { x; } }
127fn b() { bar::foo(9, 2) }
128"#,
129 r#"
130mod bar { pub fn foo(x: i32) { x; } }
131fn b() { bar::foo(9) }
132"#,
133 );
134 }
135
136 #[test]
137 fn remove_unused_turbofished_func() {
138 check_assist(
139 remove_unused_param,
140 r#"
141pub fn foo<T>(x: T, <|>y: i32) { x; }
142fn b() { foo::<i32>(9, 2) }
143"#,
144 r#"
145pub fn foo<T>(x: T) { x; }
146fn b() { foo::<i32>(9) }
147"#,
148 );
149 }
150
151 #[test]
152 fn remove_unused_generic_unused_param_func() {
153 check_assist(
154 remove_unused_param,
155 r#"
156pub fn foo<T>(x: i32, <|>y: T) { x; }
157fn b() { foo::<i32>(9, 2) }
158fn b2() { foo(9, 2) }
159"#,
160 r#"
161pub fn foo<T>(x: i32) { x; }
162fn b() { foo::<i32>(9) }
163fn b2() { foo(9) }
164"#,
165 );
166 }
167
168 #[test]
121 fn keep_used() { 169 fn keep_used() {
122 mark::check!(keep_used); 170 mark::check!(keep_used);
123 check_assist_not_applicable( 171 check_assist_not_applicable(
@@ -128,4 +176,37 @@ fn main() { foo(9, 2) }
128"#, 176"#,
129 ); 177 );
130 } 178 }
179
180 #[test]
181 fn remove_across_files() {
182 check_assist(
183 remove_unused_param,
184 r#"
185//- /main.rs
186fn foo(x: i32, <|>y: i32) { x; }
187
188mod foo;
189
190//- /foo.rs
191use super::foo;
192
193fn bar() {
194 let _ = foo(1, 2);
195}
196"#,
197 r#"
198//- /main.rs
199fn foo(x: i32) { x; }
200
201mod foo;
202
203//- /foo.rs
204use super::foo;
205
206fn bar() {
207 let _ = foo(1);
208}
209"#,
210 )
211 }
131} 212}
diff --git a/crates/assists/src/handlers/reorder_fields.rs b/crates/assists/src/handlers/reorder_fields.rs
index 527f457a7..7c0f0f44e 100644
--- a/crates/assists/src/handlers/reorder_fields.rs
+++ b/crates/assists/src/handlers/reorder_fields.rs
@@ -47,9 +47,11 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
47 "Reorder record fields", 47 "Reorder record fields",
48 target, 48 target,
49 |edit| { 49 |edit| {
50 let mut rewriter = algo::SyntaxRewriter::default();
50 for (old, new) in fields.iter().zip(&sorted_fields) { 51 for (old, new) in fields.iter().zip(&sorted_fields) {
51 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 52 rewriter.replace(old, new);
52 } 53 }
54 edit.rewrite(rewriter);
53 }, 55 },
54 ) 56 )
55} 57}
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
new file mode 100644
index 000000000..453a6cebf
--- /dev/null
+++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -0,0 +1,400 @@
1use ide_db::imports_locator;
2use itertools::Itertools;
3use syntax::{
4 ast::{self, make, AstNode},
5 Direction, SmolStr,
6 SyntaxKind::{IDENT, WHITESPACE},
7 TextSize,
8};
9
10use crate::{
11 assist_context::{AssistBuilder, AssistContext, Assists},
12 utils::{
13 add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor,
14 DefaultMethods,
15 },
16 AssistId, AssistKind,
17};
18
19// Assist: replace_derive_with_manual_impl
20//
21// Converts a `derive` impl into a manual one.
22//
23// ```
24// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
25// #[derive(Deb<|>ug, Display)]
26// struct S;
27// ```
28// ->
29// ```
30// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
31// #[derive(Display)]
32// struct S;
33//
34// impl Debug for S {
35// fn fmt(&self, f: &mut Formatter) -> Result<()> {
36// ${0:todo!()}
37// }
38// }
39// ```
40pub(crate) fn replace_derive_with_manual_impl(
41 acc: &mut Assists,
42 ctx: &AssistContext,
43) -> Option<()> {
44 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
45
46 let attr_name = attr
47 .syntax()
48 .descendants_with_tokens()
49 .filter(|t| t.kind() == IDENT)
50 .find_map(syntax::NodeOrToken::into_token)
51 .filter(|t| t.text() == "derive")?
52 .text()
53 .clone();
54
55 let trait_token =
56 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
57 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
58
59 let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
60 let insert_pos = annotated_name.syntax().parent()?.text_range().end();
61
62 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
63 let current_crate = current_module.krate();
64
65 let found_traits =
66 imports_locator::find_exact_imports(&ctx.sema, current_crate, trait_token.text())
67 .filter_map(
68 |candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
69 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
70 _ => None,
71 },
72 )
73 .flat_map(|trait_| {
74 current_module
75 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
76 .as_ref()
77 .map(mod_path_to_ast)
78 .zip(Some(trait_))
79 });
80
81 let mut no_traits_found = true;
82 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
83 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?;
84 }
85 if no_traits_found {
86 add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?;
87 }
88 Some(())
89}
90
91fn add_assist(
92 acc: &mut Assists,
93 ctx: &AssistContext,
94 attr: &ast::Attr,
95 trait_path: &ast::Path,
96 trait_: Option<hir::Trait>,
97 annotated_name: &ast::Name,
98 insert_pos: TextSize,
99) -> Option<()> {
100 let target = attr.syntax().text_range();
101 let input = attr.token_tree()?;
102 let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name);
103 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
104
105 acc.add(
106 AssistId("replace_derive_with_manual_impl", AssistKind::Refactor),
107 label,
108 target,
109 |builder| {
110 let impl_def_with_items =
111 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
112 update_attribute(builder, &input, &trait_name, &attr);
113 match (ctx.config.snippet_cap, impl_def_with_items) {
114 (None, _) => builder.insert(
115 insert_pos,
116 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
117 ),
118 (Some(cap), None) => builder.insert_snippet(
119 cap,
120 insert_pos,
121 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
122 ),
123 (Some(cap), Some((impl_def, first_assoc_item))) => {
124 let mut cursor = Cursor::Before(first_assoc_item.syntax());
125 let placeholder;
126 if let ast::AssocItem::Fn(ref func) = first_assoc_item {
127 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
128 {
129 if m.syntax().text() == "todo!()" {
130 placeholder = m;
131 cursor = Cursor::Replace(placeholder.syntax());
132 }
133 }
134 }
135
136 builder.insert_snippet(
137 cap,
138 insert_pos,
139 format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
140 )
141 }
142 };
143 },
144 )
145}
146
147fn impl_def_from_trait(
148 sema: &hir::Semantics<ide_db::RootDatabase>,
149 annotated_name: &ast::Name,
150 trait_: Option<hir::Trait>,
151 trait_path: &ast::Path,
152) -> Option<(ast::Impl, ast::AssocItem)> {
153 let trait_ = trait_?;
154 let target_scope = sema.scope(annotated_name.syntax());
155 let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No);
156 if trait_items.is_empty() {
157 return None;
158 }
159 let impl_def = make::impl_trait(
160 trait_path.clone(),
161 make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))),
162 );
163 let (impl_def, first_assoc_item) =
164 add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
165 Some((impl_def, first_assoc_item))
166}
167
168fn update_attribute(
169 builder: &mut AssistBuilder,
170 input: &ast::TokenTree,
171 trait_name: &ast::NameRef,
172 attr: &ast::Attr,
173) {
174 let new_attr_input = input
175 .syntax()
176 .descendants_with_tokens()
177 .filter(|t| t.kind() == IDENT)
178 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
179 .filter(|t| t != trait_name.text())
180 .collect::<Vec<SmolStr>>();
181 let has_more_derives = !new_attr_input.is_empty();
182
183 if has_more_derives {
184 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
185 builder.replace(input.syntax().text_range(), new_attr_input);
186 } else {
187 let attr_range = attr.syntax().text_range();
188 builder.delete(attr_range);
189
190 if let Some(line_break_range) = attr
191 .syntax()
192 .next_sibling_or_token()
193 .filter(|t| t.kind() == WHITESPACE)
194 .map(|t| t.text_range())
195 {
196 builder.delete(line_break_range);
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::tests::{check_assist, check_assist_not_applicable};
204
205 use super::*;
206
207 #[test]
208 fn add_custom_impl_debug() {
209 check_assist(
210 replace_derive_with_manual_impl,
211 "
212mod fmt {
213 pub struct Error;
214 pub type Result = Result<(), Error>;
215 pub struct Formatter<'a>;
216 pub trait Debug {
217 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
218 }
219}
220
221#[derive(Debu<|>g)]
222struct Foo {
223 bar: String,
224}
225",
226 "
227mod fmt {
228 pub struct Error;
229 pub type Result = Result<(), Error>;
230 pub struct Formatter<'a>;
231 pub trait Debug {
232 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
233 }
234}
235
236struct Foo {
237 bar: String,
238}
239
240impl fmt::Debug for Foo {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 ${0:todo!()}
243 }
244}
245",
246 )
247 }
248 #[test]
249 fn add_custom_impl_all() {
250 check_assist(
251 replace_derive_with_manual_impl,
252 "
253mod foo {
254 pub trait Bar {
255 type Qux;
256 const Baz: usize = 42;
257 const Fez: usize;
258 fn foo();
259 fn bar() {}
260 }
261}
262
263#[derive(<|>Bar)]
264struct Foo {
265 bar: String,
266}
267",
268 "
269mod foo {
270 pub trait Bar {
271 type Qux;
272 const Baz: usize = 42;
273 const Fez: usize;
274 fn foo();
275 fn bar() {}
276 }
277}
278
279struct Foo {
280 bar: String,
281}
282
283impl foo::Bar for Foo {
284 $0type Qux;
285
286 const Baz: usize = 42;
287
288 const Fez: usize;
289
290 fn foo() {
291 todo!()
292 }
293}
294",
295 )
296 }
297 #[test]
298 fn add_custom_impl_for_unique_input() {
299 check_assist(
300 replace_derive_with_manual_impl,
301 "
302#[derive(Debu<|>g)]
303struct Foo {
304 bar: String,
305}
306 ",
307 "
308struct Foo {
309 bar: String,
310}
311
312impl Debug for Foo {
313 $0
314}
315 ",
316 )
317 }
318
319 #[test]
320 fn add_custom_impl_for_with_visibility_modifier() {
321 check_assist(
322 replace_derive_with_manual_impl,
323 "
324#[derive(Debug<|>)]
325pub struct Foo {
326 bar: String,
327}
328 ",
329 "
330pub struct Foo {
331 bar: String,
332}
333
334impl Debug for Foo {
335 $0
336}
337 ",
338 )
339 }
340
341 #[test]
342 fn add_custom_impl_when_multiple_inputs() {
343 check_assist(
344 replace_derive_with_manual_impl,
345 "
346#[derive(Display, Debug<|>, Serialize)]
347struct Foo {}
348 ",
349 "
350#[derive(Display, Serialize)]
351struct Foo {}
352
353impl Debug for Foo {
354 $0
355}
356 ",
357 )
358 }
359
360 #[test]
361 fn test_ignore_derive_macro_without_input() {
362 check_assist_not_applicable(
363 replace_derive_with_manual_impl,
364 "
365#[derive(<|>)]
366struct Foo {}
367 ",
368 )
369 }
370
371 #[test]
372 fn test_ignore_if_cursor_on_param() {
373 check_assist_not_applicable(
374 replace_derive_with_manual_impl,
375 "
376#[derive<|>(Debug)]
377struct Foo {}
378 ",
379 );
380
381 check_assist_not_applicable(
382 replace_derive_with_manual_impl,
383 "
384#[derive(Debug)<|>]
385struct Foo {}
386 ",
387 )
388 }
389
390 #[test]
391 fn test_ignore_if_not_derive() {
392 check_assist_not_applicable(
393 replace_derive_with_manual_impl,
394 "
395#[allow(non_camel_<|>case_types)]
396struct Foo {}
397 ",
398 )
399 }
400}
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 d7e1d9580..a66db9ae3 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -34,7 +34,7 @@ pub(crate) fn replace_qualified_name_with_use(
34 } 34 }
35 35
36 let target = path.syntax().text_range(); 36 let target = path.syntax().text_range();
37 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 37 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
38 let syntax = scope.as_syntax_node(); 38 let syntax = scope.as_syntax_node();
39 acc.add( 39 acc.add(
40 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 40 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
diff --git a/crates/assists/src/handlers/unwrap_block.rs b/crates/assists/src/handlers/unwrap_block.rs
index 36ef871b9..676db7137 100644
--- a/crates/assists/src/handlers/unwrap_block.rs
+++ b/crates/assists/src/handlers/unwrap_block.rs
@@ -3,7 +3,7 @@ use syntax::{
3 self, 3 self,
4 edit::{AstNodeEdit, IndentLevel}, 4 edit::{AstNodeEdit, IndentLevel},
5 }, 5 },
6 AstNode, TextRange, T, 6 AstNode, SyntaxKind, TextRange, T,
7}; 7};
8 8
9use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; 9use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
@@ -31,11 +31,21 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
31 31
32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?; 32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?; 33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
34 let target = block.syntax().text_range();
34 let mut parent = block.syntax().parent()?; 35 let mut parent = block.syntax().parent()?;
35 if ast::MatchArm::can_cast(parent.kind()) { 36 if ast::MatchArm::can_cast(parent.kind()) {
36 parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? 37 parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
37 } 38 }
38 39
40 if matches!(parent.kind(), SyntaxKind::BLOCK_EXPR | SyntaxKind::EXPR_STMT) {
41 return acc.add(assist_id, assist_label, target, |builder| {
42 builder.replace(
43 block.syntax().text_range(),
44 update_expr_string(block.to_string(), &[' ', '{', '\n']),
45 );
46 });
47 }
48
39 let parent = ast::Expr::cast(parent)?; 49 let parent = ast::Expr::cast(parent)?;
40 50
41 match parent.clone() { 51 match parent.clone() {
@@ -48,7 +58,6 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
48 // For `else if` blocks 58 // For `else if` blocks
49 let ancestor_then_branch = ancestor.then_branch()?; 59 let ancestor_then_branch = ancestor.then_branch()?;
50 60
51 let target = then_branch.syntax().text_range();
52 return acc.add(assist_id, assist_label, target, |edit| { 61 return acc.add(assist_id, assist_label, target, |edit| {
53 let range_to_del_else_if = TextRange::new( 62 let range_to_del_else_if = TextRange::new(
54 ancestor_then_branch.syntax().text_range().end(), 63 ancestor_then_branch.syntax().text_range().end(),
@@ -68,7 +77,6 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
68 }); 77 });
69 } 78 }
70 } else { 79 } else {
71 let target = block.syntax().text_range();
72 return acc.add(assist_id, assist_label, target, |edit| { 80 return acc.add(assist_id, assist_label, target, |edit| {
73 let range_to_del = TextRange::new( 81 let range_to_del = TextRange::new(
74 then_branch.syntax().text_range().end(), 82 then_branch.syntax().text_range().end(),
@@ -84,7 +92,6 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
84 }; 92 };
85 93
86 let unwrapped = unwrap_trivial_block(block); 94 let unwrapped = unwrap_trivial_block(block);
87 let target = unwrapped.syntax().text_range();
88 acc.add(assist_id, assist_label, target, |builder| { 95 acc.add(assist_id, assist_label, target, |builder| {
89 builder.replace( 96 builder.replace(
90 parent.syntax().text_range(), 97 parent.syntax().text_range(),
@@ -112,31 +119,89 @@ mod tests {
112 use super::*; 119 use super::*;
113 120
114 #[test] 121 #[test]
122 fn unwrap_tail_expr_block() {
123 check_assist(
124 unwrap_block,
125 r#"
126fn main() {
127 <|>{
128 92
129 }
130}
131"#,
132 r#"
133fn main() {
134 92
135}
136"#,
137 )
138 }
139
140 #[test]
141 fn unwrap_stmt_expr_block() {
142 check_assist(
143 unwrap_block,
144 r#"
145fn main() {
146 <|>{
147 92;
148 }
149 ()
150}
151"#,
152 r#"
153fn main() {
154 92;
155 ()
156}
157"#,
158 );
159 // Pedantically, we should add an `;` here...
160 check_assist(
161 unwrap_block,
162 r#"
163fn main() {
164 <|>{
165 92
166 }
167 ()
168}
169"#,
170 r#"
171fn main() {
172 92
173 ()
174}
175"#,
176 );
177 }
178
179 #[test]
115 fn simple_if() { 180 fn simple_if() {
116 check_assist( 181 check_assist(
117 unwrap_block, 182 unwrap_block,
118 r#" 183 r#"
119 fn main() { 184fn main() {
120 bar(); 185 bar();
121 if true {<|> 186 if true {<|>
122 foo(); 187 foo();
123 188
124 //comment 189 //comment
125 bar(); 190 bar();
126 } else { 191 } else {
127 println!("bar"); 192 println!("bar");
128 } 193 }
129 } 194}
130 "#, 195"#,
131 r#" 196 r#"
132 fn main() { 197fn main() {
133 bar(); 198 bar();
134 foo(); 199 foo();
135 200
136 //comment 201 //comment
137 bar(); 202 bar();
138 } 203}
139 "#, 204"#,
140 ); 205 );
141 } 206 }
142 207
@@ -145,30 +210,30 @@ mod tests {
145 check_assist( 210 check_assist(
146 unwrap_block, 211 unwrap_block,
147 r#" 212 r#"
148 fn main() { 213fn main() {
149 bar(); 214 bar();
150 if true { 215 if true {
151 foo(); 216 foo();
152 217
153 //comment 218 //comment
154 bar(); 219 bar();
155 } else {<|> 220 } else {<|>
156 println!("bar"); 221 println!("bar");
157 } 222 }
158 } 223}
159 "#, 224"#,
160 r#" 225 r#"
161 fn main() { 226fn main() {
162 bar(); 227 bar();
163 if true { 228 if true {
164 foo(); 229 foo();
165 230
166 //comment 231 //comment
167 bar(); 232 bar();
168 } 233 }
169 println!("bar"); 234 println!("bar");
170 } 235}
171 "#, 236"#,
172 ); 237 );
173 } 238 }
174 239
@@ -177,32 +242,32 @@ mod tests {
177 check_assist( 242 check_assist(
178 unwrap_block, 243 unwrap_block,
179 r#" 244 r#"
180 fn main() { 245fn main() {
181 //bar(); 246 //bar();
182 if true { 247 if true {
183 println!("true"); 248 println!("true");
184 249
185 //comment 250 //comment
186 //bar(); 251 //bar();
187 } else if false {<|> 252 } else if false {<|>
188 println!("bar"); 253 println!("bar");
189 } else { 254 } else {
190 println!("foo"); 255 println!("foo");
191 } 256 }
192 } 257}
193 "#, 258"#,
194 r#" 259 r#"
195 fn main() { 260fn main() {
196 //bar(); 261 //bar();
197 if true { 262 if true {
198 println!("true"); 263 println!("true");
199 264
200 //comment 265 //comment
201 //bar(); 266 //bar();
202 } 267 }
203 println!("bar"); 268 println!("bar");
204 } 269}
205 "#, 270"#,
206 ); 271 );
207 } 272 }
208 273
@@ -211,34 +276,34 @@ mod tests {
211 check_assist( 276 check_assist(
212 unwrap_block, 277 unwrap_block,
213 r#" 278 r#"
214 fn main() { 279fn main() {
215 //bar(); 280 //bar();
216 if true { 281 if true {
217 println!("true"); 282 println!("true");
218 283
219 //comment 284 //comment
220 //bar(); 285 //bar();
221 } else if false { 286 } else if false {
222 println!("bar"); 287 println!("bar");
223 } else if true {<|> 288 } else if true {<|>
224 println!("foo"); 289 println!("foo");
225 } 290 }
226 } 291}
227 "#, 292"#,
228 r#" 293 r#"
229 fn main() { 294fn main() {
230 //bar(); 295 //bar();
231 if true { 296 if true {
232 println!("true"); 297 println!("true");
233 298
234 //comment 299 //comment
235 //bar(); 300 //bar();
236 } else if false { 301 } else if false {
237 println!("bar"); 302 println!("bar");
238 } 303 }
239 println!("foo"); 304 println!("foo");
240 } 305}
241 "#, 306"#,
242 ); 307 );
243 } 308 }
244 309
@@ -247,38 +312,38 @@ mod tests {
247 check_assist( 312 check_assist(
248 unwrap_block, 313 unwrap_block,
249 r#" 314 r#"
250 fn main() { 315fn main() {
251 //bar(); 316 //bar();
252 if true { 317 if true {
253 println!("true"); 318 println!("true");
254 319
255 //comment 320 //comment
256 //bar(); 321 //bar();
257 } else if false { 322 } else if false {
258 println!("bar"); 323 println!("bar");
259 } else if true { 324 } else if true {
260 println!("foo"); 325 println!("foo");
261 } else {<|> 326 } else {<|>
262 println!("else"); 327 println!("else");
263 } 328 }
264 } 329}
265 "#, 330"#,
266 r#" 331 r#"
267 fn main() { 332fn main() {
268 //bar(); 333 //bar();
269 if true { 334 if true {
270 println!("true"); 335 println!("true");
271 336
272 //comment 337 //comment
273 //bar(); 338 //bar();
274 } else if false { 339 } else if false {
275 println!("bar"); 340 println!("bar");
276 } else if true { 341 } else if true {
277 println!("foo"); 342 println!("foo");
278 } 343 }
279 println!("else"); 344 println!("else");
280 } 345}
281 "#, 346"#,
282 ); 347 );
283 } 348 }
284 349
@@ -287,36 +352,36 @@ mod tests {
287 check_assist( 352 check_assist(
288 unwrap_block, 353 unwrap_block,
289 r#" 354 r#"
290 fn main() { 355fn main() {
291 //bar(); 356 //bar();
292 if true { 357 if true {
293 println!("true"); 358 println!("true");
294 359
295 //comment 360 //comment
296 //bar(); 361 //bar();
297 } else if false { 362 } else if false {
298 println!("bar"); 363 println!("bar");
299 } else if true {<|> 364 } else if true {<|>
300 println!("foo"); 365 println!("foo");
301 } else { 366 } else {
302 println!("else"); 367 println!("else");
303 } 368 }
304 } 369}
305 "#, 370"#,
306 r#" 371 r#"
307 fn main() { 372fn main() {
308 //bar(); 373 //bar();
309 if true { 374 if true {
310 println!("true"); 375 println!("true");
311 376
312 //comment 377 //comment
313 //bar(); 378 //bar();
314 } else if false { 379 } else if false {
315 println!("bar"); 380 println!("bar");
316 } 381 }
317 println!("foo"); 382 println!("foo");
318 } 383}
319 "#, 384"#,
320 ); 385 );
321 } 386 }
322 387
@@ -325,18 +390,18 @@ mod tests {
325 check_assist_not_applicable( 390 check_assist_not_applicable(
326 unwrap_block, 391 unwrap_block,
327 r#" 392 r#"
328 fn main() { 393fn main() {
329 bar();<|> 394 bar();<|>
330 if true { 395 if true {
331 foo(); 396 foo();
332 397
333 //comment 398 //comment
334 bar(); 399 bar();
335 } else { 400 } else {
336 println!("bar"); 401 println!("bar");
337 } 402 }
338 } 403}
339 "#, 404"#,
340 ); 405 );
341 } 406 }
342 407
@@ -345,31 +410,31 @@ mod tests {
345 check_assist( 410 check_assist(
346 unwrap_block, 411 unwrap_block,
347 r#" 412 r#"
348 fn main() { 413fn main() {
349 for i in 0..5 {<|> 414 for i in 0..5 {<|>
350 if true { 415 if true {
351 foo(); 416 foo();
352 417
353 //comment 418 //comment
354 bar(); 419 bar();
355 } else { 420 } else {
356 println!("bar"); 421 println!("bar");
357 } 422 }
358 } 423 }
359 } 424}
360 "#, 425"#,
361 r#" 426 r#"
362 fn main() { 427fn main() {
363 if true { 428 if true {
364 foo(); 429 foo();
365 430
366 //comment 431 //comment
367 bar(); 432 bar();
368 } else { 433 } else {
369 println!("bar"); 434 println!("bar");
370 } 435 }
371 } 436}
372 "#, 437"#,
373 ); 438 );
374 } 439 }
375 440
@@ -378,29 +443,29 @@ mod tests {
378 check_assist( 443 check_assist(
379 unwrap_block, 444 unwrap_block,
380 r#" 445 r#"
381 fn main() { 446fn main() {
382 for i in 0..5 { 447 for i in 0..5 {
383 if true {<|> 448 if true {<|>
384 foo(); 449 foo();
385 450
386 //comment 451 //comment
387 bar(); 452 bar();
388 } else { 453 } else {
389 println!("bar"); 454 println!("bar");
390 } 455 }
391 } 456 }
392 } 457}
393 "#, 458"#,
394 r#" 459 r#"
395 fn main() { 460fn main() {
396 for i in 0..5 { 461 for i in 0..5 {
397 foo(); 462 foo();
398 463
399 //comment 464 //comment
400 bar(); 465 bar();
401 } 466 }
402 } 467}
403 "#, 468"#,
404 ); 469 );
405 } 470 }
406 471
@@ -409,31 +474,31 @@ mod tests {
409 check_assist( 474 check_assist(
410 unwrap_block, 475 unwrap_block,
411 r#" 476 r#"
412 fn main() { 477fn main() {
413 loop {<|> 478 loop {<|>
414 if true { 479 if true {
415 foo(); 480 foo();
416 481
417 //comment 482 //comment
418 bar(); 483 bar();
419 } else { 484 } else {
420 println!("bar"); 485 println!("bar");
421 } 486 }
422 } 487 }
423 } 488}
424 "#, 489"#,
425 r#" 490 r#"
426 fn main() { 491fn main() {
427 if true { 492 if true {
428 foo(); 493 foo();
429 494
430 //comment 495 //comment
431 bar(); 496 bar();
432 } else { 497 } else {
433 println!("bar"); 498 println!("bar");
434 } 499 }
435 } 500}
436 "#, 501"#,
437 ); 502 );
438 } 503 }
439 504
@@ -442,31 +507,31 @@ mod tests {
442 check_assist( 507 check_assist(
443 unwrap_block, 508 unwrap_block,
444 r#" 509 r#"
445 fn main() { 510fn main() {
446 while true {<|> 511 while true {<|>
447 if true { 512 if true {
448 foo(); 513 foo();
449 514
450 //comment 515 //comment
451 bar(); 516 bar();
452 } else { 517 } else {
453 println!("bar"); 518 println!("bar");
454 } 519 }
455 } 520 }
456 } 521}
457 "#, 522"#,
458 r#" 523 r#"
459 fn main() { 524fn main() {
460 if true { 525 if true {
461 foo(); 526 foo();
462 527
463 //comment 528 //comment
464 bar(); 529 bar();
465 } else { 530 } else {
466 println!("bar"); 531 println!("bar");
467 } 532 }
468 } 533}
469 "#, 534"#,
470 ); 535 );
471 } 536 }
472 537
@@ -499,19 +564,19 @@ fn main() {
499 check_assist_not_applicable( 564 check_assist_not_applicable(
500 unwrap_block, 565 unwrap_block,
501 r#" 566 r#"
502 fn main() { 567fn main() {
503 while true { 568 while true {
504 if true { 569 if true {
505 foo();<|> 570 foo();<|>
506 571
507 //comment 572 //comment
508 bar(); 573 bar();
509 } else { 574 } else {
510 println!("bar"); 575 println!("bar");
511 } 576 }
512 } 577 }
513 } 578}
514 "#, 579"#,
515 ); 580 );
516 } 581 }
517} 582}
diff --git a/crates/assists/src/handlers/wrap_return_type_in_result.rs b/crates/assists/src/handlers/wrap_return_type_in_result.rs
new file mode 100644
index 000000000..59e5debb1
--- /dev/null
+++ b/crates/assists/src/handlers/wrap_return_type_in_result.rs
@@ -0,0 +1,1158 @@
1use std::iter;
2
3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 match_ast, AstNode, SyntaxNode,
6};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: wrap_return_type_in_result
12//
13// Wrap the function's return type into Result.
14//
15// ```
16// fn foo() -> i32<|> { 42i32 }
17// ```
18// ->
19// ```
20// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
21// ```
22pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 let parent = ret_type.syntax().parent()?;
25 let block_expr = match_ast! {
26 match parent {
27 ast::Fn(func) => func.body()?,
28 ast::ClosureExpr(closure) => match closure.body()? {
29 Expr::BlockExpr(block) => block,
30 // closures require a block when a return type is specified
31 _ => return None,
32 },
33 _ => return None,
34 }
35 };
36
37 let type_ref = &ret_type.ty()?;
38 let ret_type_str = type_ref.syntax().text().to_string();
39 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
40 if let Some(ret_type_first_part) = first_part_ret_type {
41 if ret_type_first_part.ends_with("Result") {
42 mark::hit!(wrap_return_type_in_result_simple_return_type_already_result);
43 return None;
44 }
45 }
46
47 acc.add(
48 AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite),
49 "Wrap return type in Result",
50 type_ref.syntax().text_range(),
51 |builder| {
52 let mut tail_return_expr_collector = TailReturnCollector::new();
53 tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
54 tail_return_expr_collector.collect_tail_exprs(&block_expr);
55
56 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
57 let ok_wrapped = make::expr_call(
58 make::expr_path(make::path_unqualified(make::path_segment(make::name_ref(
59 "Ok",
60 )))),
61 make::arg_list(iter::once(ret_expr_arg.clone())),
62 );
63 builder.replace_ast(ret_expr_arg, ok_wrapped);
64 }
65
66 match ctx.config.snippet_cap {
67 Some(cap) => {
68 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
69 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
70 }
71 None => builder
72 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
73 }
74 },
75 )
76}
77
78struct TailReturnCollector {
79 exprs_to_wrap: Vec<ast::Expr>,
80}
81
82impl TailReturnCollector {
83 fn new() -> Self {
84 Self { exprs_to_wrap: vec![] }
85 }
86 /// Collect all`return` expression
87 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
88 let statements = block_expr.statements();
89 for stmt in statements {
90 let expr = match &stmt {
91 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
92 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
93 ast::Stmt::Item(_) => continue,
94 };
95 if let Some(expr) = &expr {
96 self.handle_exprs(expr, collect_break);
97 }
98 }
99
100 // Browse tail expressions for each block
101 if let Some(expr) = block_expr.expr() {
102 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
103 for last_expr in last_exprs {
104 let last_expr = match last_expr {
105 NodeType::Node(expr) => expr,
106 NodeType::Leaf(expr) => expr.syntax().clone(),
107 };
108
109 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
110 self.handle_exprs(&last_expr, collect_break);
111 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
112 let expr_stmt = match &expr_stmt {
113 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
114 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
115 ast::Stmt::Item(_) => None,
116 };
117 if let Some(expr) = &expr_stmt {
118 self.handle_exprs(expr, collect_break);
119 }
120 }
121 }
122 }
123 }
124 }
125
126 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
127 match expr {
128 Expr::BlockExpr(block_expr) => {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 Expr::ReturnExpr(ret_expr) => {
132 if let Some(ret_expr_arg) = &ret_expr.expr() {
133 self.exprs_to_wrap.push(ret_expr_arg.clone());
134 }
135 }
136 Expr::BreakExpr(break_expr) if collect_break => {
137 if let Some(break_expr_arg) = &break_expr.expr() {
138 self.exprs_to_wrap.push(break_expr_arg.clone());
139 }
140 }
141 Expr::IfExpr(if_expr) => {
142 for block in if_expr.blocks() {
143 self.collect_jump_exprs(&block, collect_break);
144 }
145 }
146 Expr::LoopExpr(loop_expr) => {
147 if let Some(block_expr) = loop_expr.loop_body() {
148 self.collect_jump_exprs(&block_expr, collect_break);
149 }
150 }
151 Expr::ForExpr(for_expr) => {
152 if let Some(block_expr) = for_expr.loop_body() {
153 self.collect_jump_exprs(&block_expr, collect_break);
154 }
155 }
156 Expr::WhileExpr(while_expr) => {
157 if let Some(block_expr) = while_expr.loop_body() {
158 self.collect_jump_exprs(&block_expr, collect_break);
159 }
160 }
161 Expr::MatchExpr(match_expr) => {
162 if let Some(arm_list) = match_expr.match_arm_list() {
163 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
164 self.handle_exprs(&expr, collect_break);
165 });
166 }
167 }
168 _ => {}
169 }
170 }
171
172 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
173 if let Some(expr) = block.expr() {
174 self.handle_exprs(&expr, true);
175 self.fetch_tail_exprs(&expr);
176 }
177 }
178
179 fn fetch_tail_exprs(&mut self, expr: &Expr) {
180 if let Some(exprs) = get_tail_expr_from_block(expr) {
181 for node_type in &exprs {
182 match node_type {
183 NodeType::Leaf(expr) => {
184 self.exprs_to_wrap.push(expr.clone());
185 }
186 NodeType::Node(expr) => {
187 if let Some(last_expr) = Expr::cast(expr.clone()) {
188 self.fetch_tail_exprs(&last_expr);
189 }
190 }
191 }
192 }
193 }
194 }
195}
196
197#[derive(Debug)]
198enum NodeType {
199 Leaf(ast::Expr),
200 Node(SyntaxNode),
201}
202
203/// Get a tail expression inside a block
204fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
205 match expr {
206 Expr::IfExpr(if_expr) => {
207 let mut nodes = vec![];
208 for block in if_expr.blocks() {
209 if let Some(block_expr) = block.expr() {
210 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
211 nodes.extend(tail_exprs);
212 }
213 } else if let Some(last_expr) = block.syntax().last_child() {
214 nodes.push(NodeType::Node(last_expr));
215 } else {
216 nodes.push(NodeType::Node(block.syntax().clone()));
217 }
218 }
219 Some(nodes)
220 }
221 Expr::LoopExpr(loop_expr) => {
222 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
223 }
224 Expr::ForExpr(for_expr) => {
225 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
226 }
227 Expr::WhileExpr(while_expr) => {
228 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
229 }
230 Expr::BlockExpr(block_expr) => {
231 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
232 }
233 Expr::MatchExpr(match_expr) => {
234 let arm_list = match_expr.match_arm_list()?;
235 let arms: Vec<NodeType> = arm_list
236 .arms()
237 .filter_map(|match_arm| match_arm.expr())
238 .map(|expr| match expr {
239 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
240 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
241 _ => match expr.syntax().last_child() {
242 Some(last_expr) => NodeType::Node(last_expr),
243 None => NodeType::Node(expr.syntax().clone()),
244 },
245 })
246 .collect();
247
248 Some(arms)
249 }
250 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e)]),
251 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
252
253 Expr::CallExpr(_)
254 | Expr::Literal(_)
255 | Expr::TupleExpr(_)
256 | Expr::ArrayExpr(_)
257 | Expr::ParenExpr(_)
258 | Expr::PathExpr(_)
259 | Expr::RecordExpr(_)
260 | Expr::IndexExpr(_)
261 | Expr::MethodCallExpr(_)
262 | Expr::AwaitExpr(_)
263 | Expr::CastExpr(_)
264 | Expr::RefExpr(_)
265 | Expr::PrefixExpr(_)
266 | Expr::RangeExpr(_)
267 | Expr::BinExpr(_)
268 | Expr::MacroCall(_)
269 | Expr::BoxExpr(_) => Some(vec![NodeType::Leaf(expr.clone())]),
270 _ => None,
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::tests::{check_assist, check_assist_not_applicable};
277
278 use super::*;
279
280 #[test]
281 fn wrap_return_type_in_result_simple() {
282 check_assist(
283 wrap_return_type_in_result,
284 r#"
285fn foo() -> i3<|>2 {
286 let test = "test";
287 return 42i32;
288}
289"#,
290 r#"
291fn foo() -> Result<i32, ${0:_}> {
292 let test = "test";
293 return Ok(42i32);
294}
295"#,
296 );
297 }
298
299 #[test]
300 fn wrap_return_type_in_result_simple_closure() {
301 check_assist(
302 wrap_return_type_in_result,
303 r#"
304fn foo() {
305 || -> i32<|> {
306 let test = "test";
307 return 42i32;
308 };
309}
310"#,
311 r#"
312fn foo() {
313 || -> Result<i32, ${0:_}> {
314 let test = "test";
315 return Ok(42i32);
316 };
317}
318"#,
319 );
320 }
321
322 #[test]
323 fn wrap_return_type_in_result_simple_return_type_bad_cursor() {
324 check_assist_not_applicable(
325 wrap_return_type_in_result,
326 r#"
327fn foo() -> i32 {
328 let test = "test";<|>
329 return 42i32;
330}
331"#,
332 );
333 }
334
335 #[test]
336 fn wrap_return_type_in_result_simple_return_type_bad_cursor_closure() {
337 check_assist_not_applicable(
338 wrap_return_type_in_result,
339 r#"
340fn foo() {
341 || -> i32 {
342 let test = "test";<|>
343 return 42i32;
344 };
345}
346"#,
347 );
348 }
349
350 #[test]
351 fn wrap_return_type_in_result_closure_non_block() {
352 check_assist_not_applicable(wrap_return_type_in_result, r#"fn foo() { || -> i<|>32 3; }"#);
353 }
354
355 #[test]
356 fn wrap_return_type_in_result_simple_return_type_already_result_std() {
357 check_assist_not_applicable(
358 wrap_return_type_in_result,
359 r#"
360fn foo() -> std::result::Result<i32<|>, String> {
361 let test = "test";
362 return 42i32;
363}
364"#,
365 );
366 }
367
368 #[test]
369 fn wrap_return_type_in_result_simple_return_type_already_result() {
370 mark::check!(wrap_return_type_in_result_simple_return_type_already_result);
371 check_assist_not_applicable(
372 wrap_return_type_in_result,
373 r#"
374fn foo() -> Result<i32<|>, String> {
375 let test = "test";
376 return 42i32;
377}
378"#,
379 );
380 }
381
382 #[test]
383 fn wrap_return_type_in_result_simple_return_type_already_result_closure() {
384 check_assist_not_applicable(
385 wrap_return_type_in_result,
386 r#"
387fn foo() {
388 || -> Result<i32<|>, String> {
389 let test = "test";
390 return 42i32;
391 };
392}
393"#,
394 );
395 }
396
397 #[test]
398 fn wrap_return_type_in_result_simple_with_cursor() {
399 check_assist(
400 wrap_return_type_in_result,
401 r#"
402fn foo() -> <|>i32 {
403 let test = "test";
404 return 42i32;
405}
406"#,
407 r#"
408fn foo() -> Result<i32, ${0:_}> {
409 let test = "test";
410 return Ok(42i32);
411}
412"#,
413 );
414 }
415
416 #[test]
417 fn wrap_return_type_in_result_simple_with_tail() {
418 check_assist(
419 wrap_return_type_in_result,
420 r#"
421fn foo() -><|> i32 {
422 let test = "test";
423 42i32
424}
425"#,
426 r#"
427fn foo() -> Result<i32, ${0:_}> {
428 let test = "test";
429 Ok(42i32)
430}
431"#,
432 );
433 }
434
435 #[test]
436 fn wrap_return_type_in_result_simple_with_tail_closure() {
437 check_assist(
438 wrap_return_type_in_result,
439 r#"
440fn foo() {
441 || -><|> i32 {
442 let test = "test";
443 42i32
444 };
445}
446"#,
447 r#"
448fn foo() {
449 || -> Result<i32, ${0:_}> {
450 let test = "test";
451 Ok(42i32)
452 };
453}
454"#,
455 );
456 }
457
458 #[test]
459 fn wrap_return_type_in_result_simple_with_tail_only() {
460 check_assist(
461 wrap_return_type_in_result,
462 r#"fn foo() -> i32<|> { 42i32 }"#,
463 r#"fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }"#,
464 );
465 }
466
467 #[test]
468 fn wrap_return_type_in_result_simple_with_tail_block_like() {
469 check_assist(
470 wrap_return_type_in_result,
471 r#"
472fn foo() -> i32<|> {
473 if true {
474 42i32
475 } else {
476 24i32
477 }
478}
479"#,
480 r#"
481fn foo() -> Result<i32, ${0:_}> {
482 if true {
483 Ok(42i32)
484 } else {
485 Ok(24i32)
486 }
487}
488"#,
489 );
490 }
491
492 #[test]
493 fn wrap_return_type_in_result_simple_without_block_closure() {
494 check_assist(
495 wrap_return_type_in_result,
496 r#"
497fn foo() {
498 || -> i32<|> {
499 if true {
500 42i32
501 } else {
502 24i32
503 }
504 };
505}
506"#,
507 r#"
508fn foo() {
509 || -> Result<i32, ${0:_}> {
510 if true {
511 Ok(42i32)
512 } else {
513 Ok(24i32)
514 }
515 };
516}
517"#,
518 );
519 }
520
521 #[test]
522 fn wrap_return_type_in_result_simple_with_nested_if() {
523 check_assist(
524 wrap_return_type_in_result,
525 r#"
526fn foo() -> i32<|> {
527 if true {
528 if false {
529 1
530 } else {
531 2
532 }
533 } else {
534 24i32
535 }
536}
537"#,
538 r#"
539fn foo() -> Result<i32, ${0:_}> {
540 if true {
541 if false {
542 Ok(1)
543 } else {
544 Ok(2)
545 }
546 } else {
547 Ok(24i32)
548 }
549}
550"#,
551 );
552 }
553
554 #[test]
555 fn wrap_return_type_in_result_simple_with_await() {
556 check_assist(
557 wrap_return_type_in_result,
558 r#"
559async fn foo() -> i<|>32 {
560 if true {
561 if false {
562 1.await
563 } else {
564 2.await
565 }
566 } else {
567 24i32.await
568 }
569}
570"#,
571 r#"
572async fn foo() -> Result<i32, ${0:_}> {
573 if true {
574 if false {
575 Ok(1.await)
576 } else {
577 Ok(2.await)
578 }
579 } else {
580 Ok(24i32.await)
581 }
582}
583"#,
584 );
585 }
586
587 #[test]
588 fn wrap_return_type_in_result_simple_with_array() {
589 check_assist(
590 wrap_return_type_in_result,
591 r#"fn foo() -> [i32;<|> 3] { [1, 2, 3] }"#,
592 r#"fn foo() -> Result<[i32; 3], ${0:_}> { Ok([1, 2, 3]) }"#,
593 );
594 }
595
596 #[test]
597 fn wrap_return_type_in_result_simple_with_cast() {
598 check_assist(
599 wrap_return_type_in_result,
600 r#"
601fn foo() -<|>> i32 {
602 if true {
603 if false {
604 1 as i32
605 } else {
606 2 as i32
607 }
608 } else {
609 24 as i32
610 }
611}
612"#,
613 r#"
614fn foo() -> Result<i32, ${0:_}> {
615 if true {
616 if false {
617 Ok(1 as i32)
618 } else {
619 Ok(2 as i32)
620 }
621 } else {
622 Ok(24 as i32)
623 }
624}
625"#,
626 );
627 }
628
629 #[test]
630 fn wrap_return_type_in_result_simple_with_tail_block_like_match() {
631 check_assist(
632 wrap_return_type_in_result,
633 r#"
634fn foo() -> i32<|> {
635 let my_var = 5;
636 match my_var {
637 5 => 42i32,
638 _ => 24i32,
639 }
640}
641"#,
642 r#"
643fn foo() -> Result<i32, ${0:_}> {
644 let my_var = 5;
645 match my_var {
646 5 => Ok(42i32),
647 _ => Ok(24i32),
648 }
649}
650"#,
651 );
652 }
653
654 #[test]
655 fn wrap_return_type_in_result_simple_with_loop_with_tail() {
656 check_assist(
657 wrap_return_type_in_result,
658 r#"
659fn foo() -> i32<|> {
660 let my_var = 5;
661 loop {
662 println!("test");
663 5
664 }
665 my_var
666}
667"#,
668 r#"
669fn foo() -> Result<i32, ${0:_}> {
670 let my_var = 5;
671 loop {
672 println!("test");
673 5
674 }
675 Ok(my_var)
676}
677"#,
678 );
679 }
680
681 #[test]
682 fn wrap_return_type_in_result_simple_with_loop_in_let_stmt() {
683 check_assist(
684 wrap_return_type_in_result,
685 r#"
686fn foo() -> i32<|> {
687 let my_var = let x = loop {
688 break 1;
689 };
690 my_var
691}
692"#,
693 r#"
694fn foo() -> Result<i32, ${0:_}> {
695 let my_var = let x = loop {
696 break 1;
697 };
698 Ok(my_var)
699}
700"#,
701 );
702 }
703
704 #[test]
705 fn wrap_return_type_in_result_simple_with_tail_block_like_match_return_expr() {
706 check_assist(
707 wrap_return_type_in_result,
708 r#"
709fn foo() -> i32<|> {
710 let my_var = 5;
711 let res = match my_var {
712 5 => 42i32,
713 _ => return 24i32,
714 };
715 res
716}
717"#,
718 r#"
719fn foo() -> Result<i32, ${0:_}> {
720 let my_var = 5;
721 let res = match my_var {
722 5 => 42i32,
723 _ => return Ok(24i32),
724 };
725 Ok(res)
726}
727"#,
728 );
729
730 check_assist(
731 wrap_return_type_in_result,
732 r#"
733fn foo() -> i32<|> {
734 let my_var = 5;
735 let res = if my_var == 5 {
736 42i32
737 } else {
738 return 24i32;
739 };
740 res
741}
742"#,
743 r#"
744fn foo() -> Result<i32, ${0:_}> {
745 let my_var = 5;
746 let res = if my_var == 5 {
747 42i32
748 } else {
749 return Ok(24i32);
750 };
751 Ok(res)
752}
753"#,
754 );
755 }
756
757 #[test]
758 fn wrap_return_type_in_result_simple_with_tail_block_like_match_deeper() {
759 check_assist(
760 wrap_return_type_in_result,
761 r#"
762fn foo() -> i32<|> {
763 let my_var = 5;
764 match my_var {
765 5 => {
766 if true {
767 42i32
768 } else {
769 25i32
770 }
771 },
772 _ => {
773 let test = "test";
774 if test == "test" {
775 return bar();
776 }
777 53i32
778 },
779 }
780}
781"#,
782 r#"
783fn foo() -> Result<i32, ${0:_}> {
784 let my_var = 5;
785 match my_var {
786 5 => {
787 if true {
788 Ok(42i32)
789 } else {
790 Ok(25i32)
791 }
792 },
793 _ => {
794 let test = "test";
795 if test == "test" {
796 return Ok(bar());
797 }
798 Ok(53i32)
799 },
800 }
801}
802"#,
803 );
804 }
805
806 #[test]
807 fn wrap_return_type_in_result_simple_with_tail_block_like_early_return() {
808 check_assist(
809 wrap_return_type_in_result,
810 r#"
811fn foo() -> i<|>32 {
812 let test = "test";
813 if test == "test" {
814 return 24i32;
815 }
816 53i32
817}
818"#,
819 r#"
820fn foo() -> Result<i32, ${0:_}> {
821 let test = "test";
822 if test == "test" {
823 return Ok(24i32);
824 }
825 Ok(53i32)
826}
827"#,
828 );
829 }
830
831 #[test]
832 fn wrap_return_type_in_result_simple_with_closure() {
833 check_assist(
834 wrap_return_type_in_result,
835 r#"
836fn foo(the_field: u32) -><|> u32 {
837 let true_closure = || { return true; };
838 if the_field < 5 {
839 let mut i = 0;
840 if true_closure() {
841 return 99;
842 } else {
843 return 0;
844 }
845 }
846 the_field
847}
848"#,
849 r#"
850fn foo(the_field: u32) -> Result<u32, ${0:_}> {
851 let true_closure = || { return true; };
852 if the_field < 5 {
853 let mut i = 0;
854 if true_closure() {
855 return Ok(99);
856 } else {
857 return Ok(0);
858 }
859 }
860 Ok(the_field)
861}
862"#,
863 );
864
865 check_assist(
866 wrap_return_type_in_result,
867 r#"
868 fn foo(the_field: u32) -> u32<|> {
869 let true_closure = || {
870 return true;
871 };
872 if the_field < 5 {
873 let mut i = 0;
874
875
876 if true_closure() {
877 return 99;
878 } else {
879 return 0;
880 }
881 }
882 let t = None;
883
884 t.unwrap_or_else(|| the_field)
885 }
886 "#,
887 r#"
888 fn foo(the_field: u32) -> Result<u32, ${0:_}> {
889 let true_closure = || {
890 return true;
891 };
892 if the_field < 5 {
893 let mut i = 0;
894
895
896 if true_closure() {
897 return Ok(99);
898 } else {
899 return Ok(0);
900 }
901 }
902 let t = None;
903
904 Ok(t.unwrap_or_else(|| the_field))
905 }
906 "#,
907 );
908 }
909
910 #[test]
911 fn wrap_return_type_in_result_simple_with_weird_forms() {
912 check_assist(
913 wrap_return_type_in_result,
914 r#"
915fn foo() -> i32<|> {
916 let test = "test";
917 if test == "test" {
918 return 24i32;
919 }
920 let mut i = 0;
921 loop {
922 if i == 1 {
923 break 55;
924 }
925 i += 1;
926 }
927}
928"#,
929 r#"
930fn foo() -> Result<i32, ${0:_}> {
931 let test = "test";
932 if test == "test" {
933 return Ok(24i32);
934 }
935 let mut i = 0;
936 loop {
937 if i == 1 {
938 break Ok(55);
939 }
940 i += 1;
941 }
942}
943"#,
944 );
945
946 check_assist(
947 wrap_return_type_in_result,
948 r#"
949fn foo() -> i32<|> {
950 let test = "test";
951 if test == "test" {
952 return 24i32;
953 }
954 let mut i = 0;
955 loop {
956 loop {
957 if i == 1 {
958 break 55;
959 }
960 i += 1;
961 }
962 }
963}
964"#,
965 r#"
966fn foo() -> Result<i32, ${0:_}> {
967 let test = "test";
968 if test == "test" {
969 return Ok(24i32);
970 }
971 let mut i = 0;
972 loop {
973 loop {
974 if i == 1 {
975 break Ok(55);
976 }
977 i += 1;
978 }
979 }
980}
981"#,
982 );
983
984 check_assist(
985 wrap_return_type_in_result,
986 r#"
987fn foo() -> i3<|>2 {
988 let test = "test";
989 let other = 5;
990 if test == "test" {
991 let res = match other {
992 5 => 43,
993 _ => return 56,
994 };
995 }
996 let mut i = 0;
997 loop {
998 loop {
999 if i == 1 {
1000 break 55;
1001 }
1002 i += 1;
1003 }
1004 }
1005}
1006"#,
1007 r#"
1008fn foo() -> Result<i32, ${0:_}> {
1009 let test = "test";
1010 let other = 5;
1011 if test == "test" {
1012 let res = match other {
1013 5 => 43,
1014 _ => return Ok(56),
1015 };
1016 }
1017 let mut i = 0;
1018 loop {
1019 loop {
1020 if i == 1 {
1021 break Ok(55);
1022 }
1023 i += 1;
1024 }
1025 }
1026}
1027"#,
1028 );
1029
1030 check_assist(
1031 wrap_return_type_in_result,
1032 r#"
1033fn foo(the_field: u32) -> u32<|> {
1034 if the_field < 5 {
1035 let mut i = 0;
1036 loop {
1037 if i > 5 {
1038 return 55u32;
1039 }
1040 i += 3;
1041 }
1042 match i {
1043 5 => return 99,
1044 _ => return 0,
1045 };
1046 }
1047 the_field
1048}
1049"#,
1050 r#"
1051fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1052 if the_field < 5 {
1053 let mut i = 0;
1054 loop {
1055 if i > 5 {
1056 return Ok(55u32);
1057 }
1058 i += 3;
1059 }
1060 match i {
1061 5 => return Ok(99),
1062 _ => return Ok(0),
1063 };
1064 }
1065 Ok(the_field)
1066}
1067"#,
1068 );
1069
1070 check_assist(
1071 wrap_return_type_in_result,
1072 r#"
1073fn foo(the_field: u32) -> u3<|>2 {
1074 if the_field < 5 {
1075 let mut i = 0;
1076 match i {
1077 5 => return 99,
1078 _ => return 0,
1079 }
1080 }
1081 the_field
1082}
1083"#,
1084 r#"
1085fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1086 if the_field < 5 {
1087 let mut i = 0;
1088 match i {
1089 5 => return Ok(99),
1090 _ => return Ok(0),
1091 }
1092 }
1093 Ok(the_field)
1094}
1095"#,
1096 );
1097
1098 check_assist(
1099 wrap_return_type_in_result,
1100 r#"
1101fn foo(the_field: u32) -> u32<|> {
1102 if the_field < 5 {
1103 let mut i = 0;
1104 if i == 5 {
1105 return 99
1106 } else {
1107 return 0
1108 }
1109 }
1110 the_field
1111}
1112"#,
1113 r#"
1114fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1115 if the_field < 5 {
1116 let mut i = 0;
1117 if i == 5 {
1118 return Ok(99)
1119 } else {
1120 return Ok(0)
1121 }
1122 }
1123 Ok(the_field)
1124}
1125"#,
1126 );
1127
1128 check_assist(
1129 wrap_return_type_in_result,
1130 r#"
1131fn foo(the_field: u32) -> <|>u32 {
1132 if the_field < 5 {
1133 let mut i = 0;
1134 if i == 5 {
1135 return 99;
1136 } else {
1137 return 0;
1138 }
1139 }
1140 the_field
1141}
1142"#,
1143 r#"
1144fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1145 if the_field < 5 {
1146 let mut i = 0;
1147 if i == 5 {
1148 return Ok(99);
1149 } else {
1150 return Ok(0);
1151 }
1152 }
1153 Ok(the_field)
1154}
1155"#,
1156 );
1157 }
1158}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index af88b3437..17e9312db 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -120,13 +120,11 @@ mod handlers {
120 120
121 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; 121 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
122 122
123 mod add_custom_impl;
124 mod add_explicit_type; 123 mod add_explicit_type;
125 mod add_missing_impl_members; 124 mod add_missing_impl_members;
126 mod add_turbo_fish; 125 mod add_turbo_fish;
127 mod apply_demorgan; 126 mod apply_demorgan;
128 mod auto_import; 127 mod auto_import;
129 mod change_return_type_to_result;
130 mod change_visibility; 128 mod change_visibility;
131 mod convert_integer_literal; 129 mod convert_integer_literal;
132 mod early_return; 130 mod early_return;
@@ -143,6 +141,7 @@ mod handlers {
143 mod generate_function; 141 mod generate_function;
144 mod generate_impl; 142 mod generate_impl;
145 mod generate_new; 143 mod generate_new;
144 mod ignore_test;
146 mod infer_function_return_type; 145 mod infer_function_return_type;
147 mod inline_local_variable; 146 mod inline_local_variable;
148 mod introduce_named_lifetime; 147 mod introduce_named_lifetime;
@@ -157,6 +156,7 @@ mod handlers {
157 mod remove_mut; 156 mod remove_mut;
158 mod remove_unused_param; 157 mod remove_unused_param;
159 mod reorder_fields; 158 mod reorder_fields;
159 mod replace_derive_with_manual_impl;
160 mod replace_if_let_with_match; 160 mod replace_if_let_with_match;
161 mod replace_impl_trait_with_generic; 161 mod replace_impl_trait_with_generic;
162 mod replace_let_with_if_let; 162 mod replace_let_with_if_let;
@@ -165,16 +165,15 @@ mod handlers {
165 mod replace_unwrap_with_match; 165 mod replace_unwrap_with_match;
166 mod split_import; 166 mod split_import;
167 mod unwrap_block; 167 mod unwrap_block;
168 mod wrap_return_type_in_result;
168 169
169 pub(crate) fn all() -> &'static [Handler] { 170 pub(crate) fn all() -> &'static [Handler] {
170 &[ 171 &[
171 // These are alphabetic for the foolish consistency 172 // These are alphabetic for the foolish consistency
172 add_custom_impl::add_custom_impl,
173 add_explicit_type::add_explicit_type, 173 add_explicit_type::add_explicit_type,
174 add_turbo_fish::add_turbo_fish, 174 add_turbo_fish::add_turbo_fish,
175 apply_demorgan::apply_demorgan, 175 apply_demorgan::apply_demorgan,
176 auto_import::auto_import, 176 auto_import::auto_import,
177 change_return_type_to_result::change_return_type_to_result,
178 change_visibility::change_visibility, 177 change_visibility::change_visibility,
179 convert_integer_literal::convert_integer_literal, 178 convert_integer_literal::convert_integer_literal,
180 early_return::convert_to_guarded_return, 179 early_return::convert_to_guarded_return,
@@ -191,6 +190,7 @@ mod handlers {
191 generate_function::generate_function, 190 generate_function::generate_function,
192 generate_impl::generate_impl, 191 generate_impl::generate_impl,
193 generate_new::generate_new, 192 generate_new::generate_new,
193 ignore_test::ignore_test,
194 infer_function_return_type::infer_function_return_type, 194 infer_function_return_type::infer_function_return_type,
195 inline_local_variable::inline_local_variable, 195 inline_local_variable::inline_local_variable,
196 introduce_named_lifetime::introduce_named_lifetime, 196 introduce_named_lifetime::introduce_named_lifetime,
@@ -208,6 +208,7 @@ mod handlers {
208 remove_mut::remove_mut, 208 remove_mut::remove_mut,
209 remove_unused_param::remove_unused_param, 209 remove_unused_param::remove_unused_param,
210 reorder_fields::reorder_fields, 210 reorder_fields::reorder_fields,
211 replace_derive_with_manual_impl::replace_derive_with_manual_impl,
211 replace_if_let_with_match::replace_if_let_with_match, 212 replace_if_let_with_match::replace_if_let_with_match,
212 replace_impl_trait_with_generic::replace_impl_trait_with_generic, 213 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
213 replace_let_with_if_let::replace_let_with_if_let, 214 replace_let_with_if_let::replace_let_with_if_let,
@@ -215,6 +216,7 @@ mod handlers {
215 replace_unwrap_with_match::replace_unwrap_with_match, 216 replace_unwrap_with_match::replace_unwrap_with_match,
216 split_import::split_import, 217 split_import::split_import,
217 unwrap_block::unwrap_block, 218 unwrap_block::unwrap_block,
219 wrap_return_type_in_result::wrap_return_type_in_result,
218 // These are manually sorted for better priorities 220 // These are manually sorted for better priorities
219 add_missing_impl_members::add_missing_impl_members, 221 add_missing_impl_members::add_missing_impl_members,
220 add_missing_impl_members::add_missing_default_members, 222 add_missing_impl_members::add_missing_default_members,
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index 849d85e76..709a34d03 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -7,7 +7,7 @@ use syntax::TextRange;
7use test_utils::{assert_eq_text, extract_offset, extract_range}; 7use test_utils::{assert_eq_text, extract_offset, extract_range};
8 8
9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; 9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
10use stdx::trim_indent; 10use stdx::{format_to, trim_indent};
11 11
12pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 12pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
13 RootDatabase::with_single_file(text) 13 RootDatabase::with_single_file(text)
@@ -98,11 +98,24 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
98 match (assist, expected) { 98 match (assist, expected) {
99 (Some(assist), ExpectedResult::After(after)) => { 99 (Some(assist), ExpectedResult::After(after)) => {
100 let mut source_change = assist.source_change; 100 let mut source_change = assist.source_change;
101 let change = source_change.source_file_edits.pop().unwrap(); 101 assert!(!source_change.source_file_edits.is_empty());
102 102 let skip_header = source_change.source_file_edits.len() == 1;
103 let mut actual = db.file_text(change.file_id).as_ref().to_owned(); 103 source_change.source_file_edits.sort_by_key(|it| it.file_id);
104 change.edit.apply(&mut actual); 104
105 assert_eq_text!(after, &actual); 105 let mut buf = String::new();
106 for source_file_edit in source_change.source_file_edits {
107 let mut text = db.file_text(source_file_edit.file_id).as_ref().to_owned();
108 source_file_edit.edit.apply(&mut text);
109 if !skip_header {
110 let sr = db.file_source_root(source_file_edit.file_id);
111 let sr = db.source_root(sr);
112 let path = sr.path_for_file(&source_file_edit.file_id).unwrap();
113 format_to!(buf, "//- {}\n", path)
114 }
115 buf.push_str(&text);
116 }
117
118 assert_eq_text!(after, &buf);
106 } 119 }
107 (Some(assist), ExpectedResult::Target(target)) => { 120 (Some(assist), ExpectedResult::Target(target)) => {
108 let range = assist.assist.target; 121 let range = assist.assist.target;
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 168e1626a..5a9d1a01b 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -3,25 +3,6 @@
3use super::check_doc_test; 3use super::check_doc_test;
4 4
5#[test] 5#[test]
6fn doctest_add_custom_impl() {
7 check_doc_test(
8 "add_custom_impl",
9 r#####"
10#[derive(Deb<|>ug, Display)]
11struct S;
12"#####,
13 r#####"
14#[derive(Display)]
15struct S;
16
17impl Debug for S {
18 $0
19}
20"#####,
21 )
22}
23
24#[test]
25fn doctest_add_explicit_type() { 6fn doctest_add_explicit_type() {
26 check_doc_test( 7 check_doc_test(
27 "add_explicit_type", 8 "add_explicit_type",
@@ -178,19 +159,6 @@ pub mod std { pub mod collections { pub struct HashMap { } } }
178} 159}
179 160
180#[test] 161#[test]
181fn doctest_change_return_type_to_result() {
182 check_doc_test(
183 "change_return_type_to_result",
184 r#####"
185fn foo() -> i32<|> { 42i32 }
186"#####,
187 r#####"
188fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
189"#####,
190 )
191}
192
193#[test]
194fn doctest_change_visibility() { 162fn doctest_change_visibility() {
195 check_doc_test( 163 check_doc_test(
196 "change_visibility", 164 "change_visibility",
@@ -506,6 +474,26 @@ impl<T: Clone> Ctx<T> {
506} 474}
507 475
508#[test] 476#[test]
477fn doctest_ignore_test() {
478 check_doc_test(
479 "ignore_test",
480 r#####"
481<|>#[test]
482fn arithmetics {
483 assert_eq!(2 + 2, 5);
484}
485"#####,
486 r#####"
487#[test]
488#[ignore]
489fn arithmetics {
490 assert_eq!(2 + 2, 5);
491}
492"#####,
493 )
494}
495
496#[test]
509fn doctest_infer_function_return_type() { 497fn doctest_infer_function_return_type() {
510 check_doc_test( 498 check_doc_test(
511 "infer_function_return_type", 499 "infer_function_return_type",
@@ -832,6 +820,29 @@ const test: Foo = Foo {foo: 1, bar: 0}
832} 820}
833 821
834#[test] 822#[test]
823fn doctest_replace_derive_with_manual_impl() {
824 check_doc_test(
825 "replace_derive_with_manual_impl",
826 r#####"
827trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
828#[derive(Deb<|>ug, Display)]
829struct S;
830"#####,
831 r#####"
832trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
833#[derive(Display)]
834struct S;
835
836impl Debug for S {
837 fn fmt(&self, f: &mut Formatter) -> Result<()> {
838 ${0:todo!()}
839 }
840}
841"#####,
842 )
843}
844
845#[test]
835fn doctest_replace_if_let_with_match() { 846fn doctest_replace_if_let_with_match() {
836 check_doc_test( 847 check_doc_test(
837 "replace_if_let_with_match", 848 "replace_if_let_with_match",
@@ -985,3 +996,16 @@ fn foo() {
985"#####, 996"#####,
986 ) 997 )
987} 998}
999
1000#[test]
1001fn doctest_wrap_return_type_in_result() {
1002 check_doc_test(
1003 "wrap_return_type_in_result",
1004 r#####"
1005fn foo() -> i32<|> { 42i32 }
1006"#####,
1007 r#####"
1008fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
1009"#####,
1010 )
1011}
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 56f925ee6..66c0cdd5f 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -4,20 +4,25 @@ pub(crate) mod import_assets;
4 4
5use std::ops; 5use std::ops;
6 6
7use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; 7use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait};
8use ide_db::RootDatabase; 8use ide_db::RootDatabase;
9use itertools::Itertools; 9use itertools::Itertools;
10use syntax::{ 10use syntax::{
11 ast::{self, make, ArgListOwner}, 11 ast::edit::AstNodeEdit,
12 ast::AttrsOwner,
13 ast::NameOwner,
14 ast::{self, edit, make, ArgListOwner},
12 AstNode, Direction, 15 AstNode, Direction,
13 SyntaxKind::*, 16 SyntaxKind::*,
14 SyntaxNode, TextSize, T, 17 SyntaxNode, TextSize, T,
15}; 18};
16 19
17use crate::assist_config::SnippetCap; 20use crate::{
21 assist_config::SnippetCap,
22 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
23};
18 24
19pub use insert_use::MergeBehaviour; 25pub use insert_use::{insert_use, ImportScope, MergeBehaviour};
20pub(crate) use insert_use::{insert_use, ImportScope};
21 26
22pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { 27pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
23 let mut segments = Vec::new(); 28 let mut segments = Vec::new();
@@ -77,6 +82,104 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
77 None 82 None
78} 83}
79 84
85/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
86/// `#[test_case(...)]`, `#[tokio::test]` and similar.
87/// Also a regular `#[test]` annotation is supported.
88///
89/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
90/// but it's better than not to have the runnables for the tests at all.
91pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
92 fn_def.attrs().find_map(|attr| {
93 let path = attr.path()?;
94 if path.syntax().text().to_string().contains("test") {
95 Some(attr)
96 } else {
97 None
98 }
99 })
100}
101
102#[derive(Copy, Clone, PartialEq)]
103pub enum DefaultMethods {
104 Only,
105 No,
106}
107
108pub fn filter_assoc_items(
109 db: &RootDatabase,
110 items: &[hir::AssocItem],
111 default_methods: DefaultMethods,
112) -> Vec<ast::AssocItem> {
113 fn has_def_name(item: &ast::AssocItem) -> bool {
114 match item {
115 ast::AssocItem::Fn(def) => def.name(),
116 ast::AssocItem::TypeAlias(def) => def.name(),
117 ast::AssocItem::Const(def) => def.name(),
118 ast::AssocItem::MacroCall(_) => None,
119 }
120 .is_some()
121 };
122
123 items
124 .iter()
125 .map(|i| match i {
126 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db).value),
127 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db).value),
128 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db).value),
129 })
130 .filter(has_def_name)
131 .filter(|it| match it {
132 ast::AssocItem::Fn(def) => matches!(
133 (default_methods, def.body()),
134 (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
135 ),
136 _ => default_methods == DefaultMethods::No,
137 })
138 .collect::<Vec<_>>()
139}
140
141pub fn add_trait_assoc_items_to_impl(
142 sema: &hir::Semantics<ide_db::RootDatabase>,
143 items: Vec<ast::AssocItem>,
144 trait_: hir::Trait,
145 impl_def: ast::Impl,
146 target_scope: hir::SemanticsScope,
147) -> (ast::Impl, ast::AssocItem) {
148 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
149
150 let n_existing_items = impl_item_list.assoc_items().count();
151 let source_scope = sema.scope_for_def(trait_);
152 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
153 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
154
155 let items = items
156 .into_iter()
157 .map(|it| ast_transform::apply(&*ast_transform, it))
158 .map(|it| match it {
159 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
160 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
161 _ => it,
162 })
163 .map(|it| edit::remove_attrs_and_docs(&it));
164
165 let new_impl_item_list = impl_item_list.append_items(items);
166 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
167 let first_new_item =
168 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
169 return (new_impl_def, first_new_item);
170
171 fn add_body(fn_def: ast::Fn) -> ast::Fn {
172 match fn_def.body() {
173 Some(_) => fn_def,
174 None => {
175 let body =
176 make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1));
177 fn_def.with_body(body)
178 }
179 }
180 }
181}
182
80#[derive(Clone, Copy, Debug)] 183#[derive(Clone, Copy, Debug)]
81pub(crate) enum Cursor<'a> { 184pub(crate) enum Cursor<'a> {
82 Replace(&'a SyntaxNode), 185 Replace(&'a SyntaxNode),
@@ -171,6 +274,12 @@ pub mod convert {
171 } 274 }
172} 275}
173 276
277pub mod default {
278 pub trait Default {
279 fn default() -> Self;
280 }
281}
282
174pub mod iter { 283pub mod iter {
175 pub use self::traits::{collect::IntoIterator, iterator::Iterator}; 284 pub use self::traits::{collect::IntoIterator, iterator::Iterator};
176 mod traits { 285 mod traits {
@@ -241,7 +350,7 @@ pub mod option {
241} 350}
242 351
243pub mod prelude { 352pub mod prelude {
244 pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}}; 353 pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}, default::Default};
245} 354}
246#[prelude_import] 355#[prelude_import]
247pub use prelude::*; 356pub use prelude::*;
@@ -259,6 +368,10 @@ pub use prelude::*;
259 self.find_enum("core:option:Option") 368 self.find_enum("core:option:Option")
260 } 369 }
261 370
371 pub fn core_default_Default(&self) -> Option<Trait> {
372 self.find_trait("core:default:Default")
373 }
374
262 pub fn core_iter_Iterator(&self) -> Option<Trait> { 375 pub fn core_iter_Iterator(&self) -> Option<Trait> {
263 self.find_trait("core:iter:traits:iterator:Iterator") 376 self.find_trait("core:iter:traits:iterator:Iterator")
264 } 377 }
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
index f47edbb76..ff5c0e78e 100644
--- a/crates/assists/src/utils/import_assets.rs
+++ b/crates/assists/src/utils/import_assets.rs
@@ -179,21 +179,25 @@ impl ImportAssets {
179 } 179 }
180 }; 180 };
181 181
182 let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query()) 182 let mut res =
183 .into_iter() 183 imports_locator::find_exact_imports(sema, current_crate, &self.get_search_query())
184 .filter_map(filter) 184 .filter_map(filter)
185 .filter_map(|candidate| { 185 .filter_map(|candidate| {
186 let item: hir::ItemInNs = candidate.either(Into::into, Into::into); 186 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
187 if let Some(prefix_kind) = prefixed { 187 if let Some(prefix_kind) = prefixed {
188 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind) 188 self.module_with_name_to_import.find_use_path_prefixed(
189 } else { 189 db,
190 self.module_with_name_to_import.find_use_path(db, item) 190 item,
191 } 191 prefix_kind,
192 .map(|path| (path, item)) 192 )
193 }) 193 } else {
194 .filter(|(use_path, _)| !use_path.segments.is_empty()) 194 self.module_with_name_to_import.find_use_path(db, item)
195 .take(20) 195 }
196 .collect::<Vec<_>>(); 196 .map(|path| (path, item))
197 })
198 .filter(|(use_path, _)| use_path.len() > 1)
199 .take(20)
200 .collect::<Vec<_>>();
197 res.sort_by_key(|(path, _)| path.clone()); 201 res.sort_by_key(|(path, _)| path.clone());
198 res 202 res
199 } 203 }
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 84a0dffdd..423782a0e 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,6 +1,8 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2use std::{cmp::Ordering, iter::successors}; 2use std::{cmp::Ordering, iter::successors};
3 3
4use hir::Semantics;
5use ide_db::RootDatabase;
4use itertools::{EitherOrBoth, Itertools}; 6use itertools::{EitherOrBoth, Itertools};
5use syntax::{ 7use syntax::{
6 algo::SyntaxRewriter, 8 algo::SyntaxRewriter,
@@ -9,12 +11,12 @@ use syntax::{
9 edit::{AstNodeEdit, IndentLevel}, 11 edit::{AstNodeEdit, IndentLevel},
10 make, AstNode, PathSegmentKind, VisibilityOwner, 12 make, AstNode, PathSegmentKind, VisibilityOwner,
11 }, 13 },
12 InsertPosition, SyntaxElement, SyntaxNode, 14 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
13}; 15};
14use test_utils::mark; 16use test_utils::mark;
15 17
16#[derive(Debug)] 18#[derive(Debug, Clone)]
17pub(crate) enum ImportScope { 19pub enum ImportScope {
18 File(ast::SourceFile), 20 File(ast::SourceFile),
19 Module(ast::ItemList), 21 Module(ast::ItemList),
20} 22}
@@ -31,14 +33,14 @@ impl ImportScope {
31 } 33 }
32 34
33 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. 35 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
34 pub(crate) fn find_insert_use_container( 36 pub fn find_insert_use_container(
35 position: &SyntaxNode, 37 position: &SyntaxNode,
36 ctx: &crate::assist_context::AssistContext, 38 sema: &Semantics<'_, RootDatabase>,
37 ) -> Option<Self> { 39 ) -> Option<Self> {
38 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from) 40 sema.ancestors_with_macros(position.clone()).find_map(Self::from)
39 } 41 }
40 42
41 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode { 43 pub fn as_syntax_node(&self) -> &SyntaxNode {
42 match self { 44 match self {
43 ImportScope::File(file) => file.syntax(), 45 ImportScope::File(file) => file.syntax(),
44 ImportScope::Module(item_list) => item_list.syntax(), 46 ImportScope::Module(item_list) => item_list.syntax(),
@@ -63,29 +65,32 @@ impl ImportScope {
63 } 65 }
64 } 66 }
65 67
66 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { 68 fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
67 // check if the scope has inner attributes, we dont want to insert in front of them 69 self.as_syntax_node()
68 match self 70 .children_with_tokens()
69 .as_syntax_node() 71 .filter(|child| match child {
70 .children() 72 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
71 // no flat_map here cause we want to short circuit the iterator 73 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
72 .map(ast::Attr::cast)
73 .take_while(|attr| {
74 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
75 }) 74 })
76 .last() 75 .last()
77 .flatten() 76 .map(|last_inner_element| {
78 { 77 (InsertPosition::After(last_inner_element.into()), AddBlankLine::BeforeTwice)
79 Some(attr) => { 78 })
80 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice) 79 .unwrap_or_else(|| self.first_insert_pos())
81 }
82 None => self.first_insert_pos(),
83 }
84 } 80 }
85} 81}
86 82
83fn is_inner_attribute(node: SyntaxNode) -> bool {
84 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
85}
86
87fn is_inner_comment(token: SyntaxToken) -> bool {
88 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
89 == Some(ast::CommentPlacement::Inner)
90}
91
87/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 92/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
88pub(crate) fn insert_use<'a>( 93pub fn insert_use<'a>(
89 scope: &ImportScope, 94 scope: &ImportScope,
90 path: ast::Path, 95 path: ast::Path,
91 merge: Option<MergeBehaviour>, 96 merge: Option<MergeBehaviour>,
@@ -558,7 +563,7 @@ fn find_insert_position(
558 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) 563 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
559 } 564 }
560 // there are no imports in this file at all 565 // there are no imports in this file at all
561 None => scope.insert_pos_after_inner_attribute(), 566 None => scope.insert_pos_after_last_inner_element(),
562 }, 567 },
563 } 568 }
564 } 569 }
@@ -830,12 +835,67 @@ use foo::bar;",
830 "foo::bar", 835 "foo::bar",
831 r"#![allow(unused_imports)] 836 r"#![allow(unused_imports)]
832 837
838#![no_std]
833fn main() {}", 839fn main() {}",
834 r"#![allow(unused_imports)] 840 r"#![allow(unused_imports)]
835 841
836use foo::bar; 842#![no_std]
837 843
844use foo::bar;
838fn main() {}", 845fn main() {}",
846 );
847 }
848
849 #[test]
850 fn inserts_after_single_line_inner_comments() {
851 check_none(
852 "foo::bar::Baz",
853 "//! Single line inner comments do not allow any code before them.",
854 r#"//! Single line inner comments do not allow any code before them.
855
856use foo::bar::Baz;"#,
857 );
858 }
859
860 #[test]
861 fn inserts_after_multiline_inner_comments() {
862 check_none(
863 "foo::bar::Baz",
864 r#"/*! Multiline inner comments do not allow any code before them. */
865
866/*! Still an inner comment, cannot place any code before. */
867fn main() {}"#,
868 r#"/*! Multiline inner comments do not allow any code before them. */
869
870/*! Still an inner comment, cannot place any code before. */
871
872use foo::bar::Baz;
873fn main() {}"#,
874 )
875 }
876
877 #[test]
878 fn inserts_after_all_inner_items() {
879 check_none(
880 "foo::bar::Baz",
881 r#"#![allow(unused_imports)]
882/*! Multiline line comment 2 */
883
884
885//! Single line comment 1
886#![no_std]
887//! Single line comment 2
888fn main() {}"#,
889 r#"#![allow(unused_imports)]
890/*! Multiline line comment 2 */
891
892
893//! Single line comment 1
894#![no_std]
895//! Single line comment 2
896
897use foo::bar::Baz;
898fn main() {}"#,
839 ) 899 )
840 } 900 }
841 901
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index 31907ed98..98ba372ad 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -225,7 +225,10 @@ impl CrateGraph {
225 to: CrateId, 225 to: CrateId,
226 ) -> Result<(), CyclicDependenciesError> { 226 ) -> Result<(), CyclicDependenciesError> {
227 if self.dfs_find(from, to, &mut FxHashSet::default()) { 227 if self.dfs_find(from, to, &mut FxHashSet::default()) {
228 return Err(CyclicDependenciesError); 228 return Err(CyclicDependenciesError {
229 from: (from, self[from].display_name.clone()),
230 to: (to, self[to].display_name.clone()),
231 });
229 } 232 }
230 self.arena.get_mut(&from).unwrap().add_dep(name, to); 233 self.arena.get_mut(&from).unwrap().add_dep(name, to);
231 Ok(()) 234 Ok(())
@@ -421,7 +424,20 @@ impl fmt::Display for ParseEditionError {
421impl std::error::Error for ParseEditionError {} 424impl std::error::Error for ParseEditionError {}
422 425
423#[derive(Debug)] 426#[derive(Debug)]
424pub struct CyclicDependenciesError; 427pub struct CyclicDependenciesError {
428 from: (CrateId, Option<CrateDisplayName>),
429 to: (CrateId, Option<CrateDisplayName>),
430}
431
432impl fmt::Display for CyclicDependenciesError {
433 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434 let render = |(id, name): &(CrateId, Option<CrateDisplayName>)| match name {
435 Some(it) => format!("{}({:?})", it, id),
436 None => format!("{:?}", id),
437 };
438 write!(f, "cyclic deps: {} -> {}", render(&self.from), render(&self.to))
439 }
440}
425 441
426#[cfg(test)] 442#[cfg(test)]
427mod tests { 443mod tests {
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index d0e08cf5f..d88ecf8b0 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -41,12 +41,6 @@ impl CfgOptions {
41 self.enabled.insert(CfgAtom::KeyValue { key, value }); 41 self.enabled.insert(CfgAtom::KeyValue { key, value });
42 } 42 }
43 43
44 pub fn append(&mut self, other: &CfgOptions) {
45 for atom in &other.enabled {
46 self.enabled.insert(atom.clone());
47 }
48 }
49
50 pub fn apply_diff(&mut self, diff: CfgDiff) { 44 pub fn apply_diff(&mut self, diff: CfgDiff) {
51 for atom in diff.enable { 45 for atom in diff.enable {
52 self.enabled.insert(atom); 46 self.enabled.insert(atom);
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml
index b79ee33f7..e7df9d955 100644
--- a/crates/completion/Cargo.toml
+++ b/crates/completion/Cargo.toml
@@ -13,7 +13,9 @@ doctest = false
13itertools = "0.9.0" 13itertools = "0.9.0"
14log = "0.4.8" 14log = "0.4.8"
15rustc-hash = "1.1.0" 15rustc-hash = "1.1.0"
16either = "1.6.1"
16 17
18assists = { path = "../assists", version = "0.0.0" }
17stdx = { path = "../stdx", version = "0.0.0" } 19stdx = { path = "../stdx", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" } 20syntax = { path = "../syntax", version = "0.0.0" }
19text_edit = { path = "../text_edit", version = "0.0.0" } 21text_edit = { path = "../text_edit", version = "0.0.0" }
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs
index 75dbb1a23..9b7d6c580 100644
--- a/crates/completion/src/completions.rs
+++ b/crates/completion/src/completions.rs
@@ -90,7 +90,7 @@ impl Completions {
90 Some(it) => it, 90 Some(it) => it,
91 None => return, 91 None => return,
92 }; 92 };
93 if let Some(item) = render_macro(RenderContext::new(ctx), name, macro_) { 93 if let Some(item) = render_macro(RenderContext::new(ctx), None, name, macro_) {
94 self.add(item); 94 self.add(item);
95 } 95 }
96 } 96 }
@@ -101,7 +101,7 @@ impl Completions {
101 func: hir::Function, 101 func: hir::Function,
102 local_name: Option<String>, 102 local_name: Option<String>,
103 ) { 103 ) {
104 let item = render_fn(RenderContext::new(ctx), local_name, func); 104 let item = render_fn(RenderContext::new(ctx), None, local_name, func);
105 self.add(item) 105 self.add(item)
106 } 106 }
107 107
@@ -123,7 +123,7 @@ impl Completions {
123 variant: hir::EnumVariant, 123 variant: hir::EnumVariant,
124 path: ModPath, 124 path: ModPath,
125 ) { 125 ) {
126 let item = render_enum_variant(RenderContext::new(ctx), None, variant, Some(path)); 126 let item = render_enum_variant(RenderContext::new(ctx), None, None, variant, Some(path));
127 self.add(item); 127 self.add(item);
128 } 128 }
129 129
@@ -133,7 +133,7 @@ impl Completions {
133 variant: hir::EnumVariant, 133 variant: hir::EnumVariant,
134 local_name: Option<String>, 134 local_name: Option<String>,
135 ) { 135 ) {
136 let item = render_enum_variant(RenderContext::new(ctx), local_name, variant, None); 136 let item = render_enum_variant(RenderContext::new(ctx), None, local_name, variant, None);
137 self.add(item); 137 self.add(item);
138 } 138 }
139} 139}
diff --git a/crates/completion/src/completions/keyword.rs b/crates/completion/src/completions/keyword.rs
index c7df15900..720349b9d 100644
--- a/crates/completion/src/completions/keyword.rs
+++ b/crates/completion/src/completions/keyword.rs
@@ -44,6 +44,10 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
44 mark::hit!(no_keyword_completion_in_comments); 44 mark::hit!(no_keyword_completion_in_comments);
45 return; 45 return;
46 } 46 }
47 if ctx.record_lit_syntax.is_some() {
48 mark::hit!(no_keyword_completion_in_record_lit);
49 return;
50 }
47 51
48 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; 52 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent;
49 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { 53 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling {
@@ -563,4 +567,46 @@ struct Foo {
563 "#]], 567 "#]],
564 ) 568 )
565 } 569 }
570
571 #[test]
572 fn skip_struct_initializer() {
573 mark::check!(no_keyword_completion_in_record_lit);
574 check(
575 r#"
576struct Foo {
577 pub f: i32,
578}
579fn foo() {
580 Foo {
581 <|>
582 }
583}
584"#,
585 expect![[r#""#]],
586 );
587 }
588
589 #[test]
590 fn struct_initializer_field_expr() {
591 check(
592 r#"
593struct Foo {
594 pub f: i32,
595}
596fn foo() {
597 Foo {
598 f: <|>
599 }
600}
601"#,
602 expect![[r#"
603 kw if
604 kw if let
605 kw loop
606 kw match
607 kw return
608 kw while
609 "#]],
610 );
611 }
566} 612}
diff --git a/crates/completion/src/completions/postfix.rs b/crates/completion/src/completions/postfix.rs
index 348f017bd..7fbda7a6b 100644
--- a/crates/completion/src/completions/postfix.rs
+++ b/crates/completion/src/completions/postfix.rs
@@ -184,6 +184,16 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
184 ctx, 184 ctx,
185 cap, 185 cap,
186 &dot_receiver, 186 &dot_receiver,
187 "some",
188 "Some(expr)",
189 &format!("Some({})", receiver_text),
190 )
191 .add_to(acc);
192
193 postfix_snippet(
194 ctx,
195 cap,
196 &dot_receiver,
187 "dbg", 197 "dbg",
188 "dbg!(expr)", 198 "dbg!(expr)",
189 &format!("dbg!({})", receiver_text), 199 &format!("dbg!({})", receiver_text),
@@ -291,6 +301,7 @@ fn main() {
291 sn ok Ok(expr) 301 sn ok Ok(expr)
292 sn ref &expr 302 sn ref &expr
293 sn refm &mut expr 303 sn refm &mut expr
304 sn some Some(expr)
294 sn while while expr {} 305 sn while while expr {}
295 "#]], 306 "#]],
296 ); 307 );
@@ -314,6 +325,7 @@ fn main() {
314 sn ok Ok(expr) 325 sn ok Ok(expr)
315 sn ref &expr 326 sn ref &expr
316 sn refm &mut expr 327 sn refm &mut expr
328 sn some Some(expr)
317 "#]], 329 "#]],
318 ) 330 )
319 } 331 }
diff --git a/crates/completion/src/completions/record.rs b/crates/completion/src/completions/record.rs
index 0f611084b..2049b9d09 100644
--- a/crates/completion/src/completions/record.rs
+++ b/crates/completion/src/completions/record.rs
@@ -1,16 +1,43 @@
1//! Complete fields in record literals and patterns. 1//! Complete fields in record literals and patterns.
2use crate::{CompletionContext, Completions}; 2use assists::utils::FamousDefs;
3use syntax::ast::Expr;
4
5use crate::{
6 item::CompletionKind, CompletionContext, CompletionItem, CompletionItemKind, Completions,
7};
3 8
4pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 9pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
5 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { 10 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
6 (None, None) => return None, 11 (None, None) => return None,
7 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), 12 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
8 (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), 13 (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat),
9 (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit), 14 (_, Some(record_lit)) => {
15 let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone()));
16 let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default();
17 let impl_default_trait = default_trait
18 .and_then(|default_trait| ty.map(|ty| ty.impls_trait(ctx.db, default_trait, &[])))
19 .unwrap_or(false);
20
21 let missing_fields = ctx.sema.record_literal_missing_fields(record_lit);
22 if impl_default_trait && !missing_fields.is_empty() {
23 acc.add(
24 CompletionItem::new(
25 CompletionKind::Snippet,
26 ctx.source_range(),
27 "..Default::default()",
28 )
29 .insert_text("..Default::default()")
30 .kind(CompletionItemKind::Field)
31 .build(),
32 );
33 }
34
35 missing_fields
36 }
10 }; 37 };
11 38
12 for (field, ty) in missing_fields { 39 for (field, ty) in missing_fields {
13 acc.add_field(ctx, field, &ty) 40 acc.add_field(ctx, field, &ty);
14 } 41 }
15 42
16 Some(()) 43 Some(())
@@ -18,6 +45,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
18 45
19#[cfg(test)] 46#[cfg(test)]
20mod tests { 47mod tests {
48 use assists::utils::FamousDefs;
21 use expect_test::{expect, Expect}; 49 use expect_test::{expect, Expect};
22 50
23 use crate::{test_utils::completion_list, CompletionKind}; 51 use crate::{test_utils::completion_list, CompletionKind};
@@ -27,6 +55,80 @@ mod tests {
27 expect.assert_eq(&actual); 55 expect.assert_eq(&actual);
28 } 56 }
29 57
58 fn check_snippet(ra_fixture: &str, expect: Expect) {
59 let actual = completion_list(
60 &format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE),
61 CompletionKind::Snippet,
62 );
63 expect.assert_eq(&actual);
64 }
65
66 #[test]
67 fn test_record_literal_field_default() {
68 let test_code = r#"
69struct S { foo: u32, bar: usize }
70
71impl core::default::Default for S {
72 fn default() -> Self {
73 S {
74 foo: 0,
75 bar: 0,
76 }
77 }
78}
79
80fn process(f: S) {
81 let other = S {
82 foo: 5,
83 .<|>
84 };
85}
86"#;
87 check(
88 test_code,
89 expect![[r#"
90 fd bar usize
91 "#]],
92 );
93
94 check_snippet(
95 test_code,
96 expect![[r#"
97 fd ..Default::default()
98 sn pd
99 sn ppd
100 "#]],
101 );
102 }
103
104 #[test]
105 fn test_record_literal_field_without_default() {
106 let test_code = r#"
107struct S { foo: u32, bar: usize }
108
109fn process(f: S) {
110 let other = S {
111 foo: 5,
112 .<|>
113 };
114}
115"#;
116 check(
117 test_code,
118 expect![[r#"
119 fd bar usize
120 "#]],
121 );
122
123 check_snippet(
124 test_code,
125 expect![[r#"
126 sn pd
127 sn ppd
128 "#]],
129 );
130 }
131
30 #[test] 132 #[test]
31 fn test_record_pattern_field() { 133 fn test_record_pattern_field() {
32 check( 134 check(
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index 7df58e1da..4f1c9faa0 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -1,10 +1,16 @@
1//! Completion of names from the current scope, e.g. locals and imported items. 1//! Completion of names from the current scope, e.g. locals and imported items.
2 2
3use assists::utils::ImportScope;
4use either::Either;
3use hir::{Adt, ModuleDef, ScopeDef, Type}; 5use hir::{Adt, ModuleDef, ScopeDef, Type};
6use ide_db::imports_locator;
4use syntax::AstNode; 7use syntax::AstNode;
5use test_utils::mark; 8use test_utils::mark;
6 9
7use crate::{CompletionContext, Completions}; 10use crate::{
11 render::{render_resolution_with_import, RenderContext},
12 CompletionContext, Completions,
13};
8 14
9pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 15pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { 16 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
@@ -37,6 +43,10 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
37 } 43 }
38 acc.add_resolution(ctx, name.to_string(), &res) 44 acc.add_resolution(ctx, name.to_string(), &res)
39 }); 45 });
46
47 if ctx.config.enable_experimental_completions {
48 fuzzy_completion(acc, ctx).unwrap_or_default()
49 }
40} 50}
41 51
42fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { 52fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
@@ -63,6 +73,45 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
63 } 73 }
64} 74}
65 75
76fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
77 let _p = profile::span("fuzzy_completion");
78 let current_module = ctx.scope.module()?;
79 let anchor = ctx.name_ref_syntax.as_ref()?;
80 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
81
82 let potential_import_name = ctx.token.to_string();
83
84 let possible_imports =
85 imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400)
86 .filter_map(|import_candidate| match import_candidate {
87 // when completing outside the use declaration, modules are pretty useless
88 // and tend to bloat the completion suggestions a lot
89 Either::Left(ModuleDef::Module(_)) => None,
90 Either::Left(module_def) => Some((
91 current_module.find_use_path(ctx.db, module_def)?,
92 ScopeDef::ModuleDef(module_def),
93 )),
94 Either::Right(macro_def) => Some((
95 current_module.find_use_path(ctx.db, macro_def)?,
96 ScopeDef::MacroDef(macro_def),
97 )),
98 })
99 .filter(|(mod_path, _)| mod_path.len() > 1)
100 .filter_map(|(import_path, definition)| {
101 render_resolution_with_import(
102 RenderContext::new(ctx),
103 import_path.clone(),
104 import_scope.clone(),
105 ctx.config.merge,
106 &definition,
107 )
108 })
109 .take(20);
110
111 acc.add_all(possible_imports);
112 Some(())
113}
114
66#[cfg(test)] 115#[cfg(test)]
67mod tests { 116mod tests {
68 use expect_test::{expect, Expect}; 117 use expect_test::{expect, Expect};
@@ -676,4 +725,85 @@ impl My<|>
676 "#]], 725 "#]],
677 ) 726 )
678 } 727 }
728
729 #[test]
730 fn function_fuzzy_completion() {
731 check_edit(
732 "stdin",
733 r#"
734//- /lib.rs crate:dep
735pub mod io {
736 pub fn stdin() {}
737};
738
739//- /main.rs crate:main deps:dep
740fn main() {
741 stdi<|>
742}
743"#,
744 r#"
745use dep::io::stdin;
746
747fn main() {
748 stdin()$0
749}
750"#,
751 );
752 }
753
754 #[test]
755 fn macro_fuzzy_completion() {
756 check_edit(
757 "macro_with_curlies!",
758 r#"
759//- /lib.rs crate:dep
760/// Please call me as macro_with_curlies! {}
761#[macro_export]
762macro_rules! macro_with_curlies {
763 () => {}
764}
765
766//- /main.rs crate:main deps:dep
767fn main() {
768 curli<|>
769}
770"#,
771 r#"
772use dep::macro_with_curlies;
773
774fn main() {
775 macro_with_curlies! {$0}
776}
777"#,
778 );
779 }
780
781 #[test]
782 fn struct_fuzzy_completion() {
783 check_edit(
784 "ThirdStruct",
785 r#"
786//- /lib.rs crate:dep
787pub struct FirstStruct;
788pub mod some_module {
789 pub struct SecondStruct;
790 pub struct ThirdStruct;
791}
792
793//- /main.rs crate:main deps:dep
794use dep::{FirstStruct, some_module::SecondStruct};
795
796fn main() {
797 this<|>
798}
799"#,
800 r#"
801use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
802
803fn main() {
804 ThirdStruct
805}
806"#,
807 );
808 }
679} 809}
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs
index 71b49ace8..f50735372 100644
--- a/crates/completion/src/config.rs
+++ b/crates/completion/src/config.rs
@@ -4,12 +4,16 @@
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//! completions if we are allowed to. 5//! completions if we are allowed to.
6 6
7use assists::utils::MergeBehaviour;
8
7#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct CompletionConfig { 10pub struct CompletionConfig {
9 pub enable_postfix_completions: bool, 11 pub enable_postfix_completions: bool,
12 pub enable_experimental_completions: bool,
10 pub add_call_parenthesis: bool, 13 pub add_call_parenthesis: bool,
11 pub add_call_argument_snippets: bool, 14 pub add_call_argument_snippets: bool,
12 pub snippet_cap: Option<SnippetCap>, 15 pub snippet_cap: Option<SnippetCap>,
16 pub merge: Option<MergeBehaviour>,
13} 17}
14 18
15impl CompletionConfig { 19impl CompletionConfig {
@@ -27,9 +31,11 @@ impl Default for CompletionConfig {
27 fn default() -> Self { 31 fn default() -> Self {
28 CompletionConfig { 32 CompletionConfig {
29 enable_postfix_completions: true, 33 enable_postfix_completions: true,
34 enable_experimental_completions: true,
30 add_call_parenthesis: true, 35 add_call_parenthesis: true,
31 add_call_argument_snippets: true, 36 add_call_argument_snippets: true,
32 snippet_cap: Some(SnippetCap { _private: () }), 37 snippet_cap: Some(SnippetCap { _private: () }),
38 merge: Some(MergeBehaviour::Full),
33 } 39 }
34 } 40 }
35} 41}
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index 6d1d085f4..b13c3f376 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -2,8 +2,9 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use hir::{Documentation, Mutability}; 5use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour};
6use syntax::TextRange; 6use hir::{Documentation, ModPath, Mutability};
7use syntax::{algo, TextRange};
7use text_edit::TextEdit; 8use text_edit::TextEdit;
8 9
9use crate::config::SnippetCap; 10use crate::config::SnippetCap;
@@ -31,6 +32,7 @@ pub struct CompletionItem {
31 /// 32 ///
32 /// Typically, replaces `source_range` with new identifier. 33 /// Typically, replaces `source_range` with new identifier.
33 text_edit: TextEdit, 34 text_edit: TextEdit,
35
34 insert_text_format: InsertTextFormat, 36 insert_text_format: InsertTextFormat,
35 37
36 /// What item (struct, function, etc) are we completing. 38 /// What item (struct, function, etc) are we completing.
@@ -199,8 +201,10 @@ impl CompletionItem {
199 trigger_call_info: None, 201 trigger_call_info: None,
200 score: None, 202 score: None,
201 ref_match: None, 203 ref_match: None,
204 import_data: None,
202 } 205 }
203 } 206 }
207
204 /// What user sees in pop-up in the UI. 208 /// What user sees in pop-up in the UI.
205 pub fn label(&self) -> &str { 209 pub fn label(&self) -> &str {
206 &self.label 210 &self.label
@@ -257,6 +261,7 @@ impl CompletionItem {
257pub(crate) struct Builder { 261pub(crate) struct Builder {
258 source_range: TextRange, 262 source_range: TextRange,
259 completion_kind: CompletionKind, 263 completion_kind: CompletionKind,
264 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
260 label: String, 265 label: String,
261 insert_text: Option<String>, 266 insert_text: Option<String>,
262 insert_text_format: InsertTextFormat, 267 insert_text_format: InsertTextFormat,
@@ -273,23 +278,50 @@ pub(crate) struct Builder {
273 278
274impl Builder { 279impl Builder {
275 pub(crate) fn build(self) -> CompletionItem { 280 pub(crate) fn build(self) -> CompletionItem {
276 let label = self.label; 281 let mut label = self.label;
277 let text_edit = match self.text_edit { 282 let mut lookup = self.lookup;
283 let mut insert_text = self.insert_text;
284 let mut text_edits = TextEdit::builder();
285
286 if let Some((import_path, import_scope, merge_behaviour)) = self.import_data {
287 let import = mod_path_to_ast(&import_path);
288 let mut import_path_without_last_segment = import_path;
289 let _ = import_path_without_last_segment.segments.pop();
290
291 if !import_path_without_last_segment.segments.is_empty() {
292 if lookup.is_none() {
293 lookup = Some(label.clone());
294 }
295 if insert_text.is_none() {
296 insert_text = Some(label.clone());
297 }
298 label = format!("{}::{}", import_path_without_last_segment, label);
299 }
300
301 let rewriter = insert_use(&import_scope, import, merge_behaviour);
302 if let Some(old_ast) = rewriter.rewrite_root() {
303 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits);
304 }
305 }
306
307 let original_edit = match self.text_edit {
278 Some(it) => it, 308 Some(it) => it,
279 None => TextEdit::replace( 309 None => {
280 self.source_range, 310 TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
281 self.insert_text.unwrap_or_else(|| label.clone()), 311 }
282 ),
283 }; 312 };
284 313
314 let mut resulting_edit = text_edits.finish();
315 resulting_edit.union(original_edit).expect("Failed to unite text edits");
316
285 CompletionItem { 317 CompletionItem {
286 source_range: self.source_range, 318 source_range: self.source_range,
287 label, 319 label,
288 insert_text_format: self.insert_text_format, 320 insert_text_format: self.insert_text_format,
289 text_edit, 321 text_edit: resulting_edit,
290 detail: self.detail, 322 detail: self.detail,
291 documentation: self.documentation, 323 documentation: self.documentation,
292 lookup: self.lookup, 324 lookup,
293 kind: self.kind, 325 kind: self.kind,
294 completion_kind: self.completion_kind, 326 completion_kind: self.completion_kind,
295 deprecated: self.deprecated.unwrap_or(false), 327 deprecated: self.deprecated.unwrap_or(false),
@@ -358,6 +390,13 @@ impl Builder {
358 self.trigger_call_info = Some(true); 390 self.trigger_call_info = Some(true);
359 self 391 self
360 } 392 }
393 pub(crate) fn import_data(
394 mut self,
395 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
396 ) -> Builder {
397 self.import_data = import_data;
398 self
399 }
361 pub(crate) fn set_ref_match( 400 pub(crate) fn set_ref_match(
362 mut self, 401 mut self,
363 ref_match: Option<(Mutability, CompletionScore)>, 402 ref_match: Option<(Mutability, CompletionScore)>,
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index cb6e0554e..aecc1378b 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -67,6 +67,13 @@ pub use crate::{
67// fn test_name() {} 67// fn test_name() {}
68// } 68// }
69// ``` 69// ```
70//
71// And experimental completions, enabled with the `rust-analyzer.completion.enableExperimental` setting.
72// This flag enables or disables:
73//
74// - Auto import: additional completion options with automatic `use` import and options from all project importable items, matched for the input
75//
76// Experimental completions might cause issues with performance and completion list look.
70 77
71/// Main entry point for completion. We run completion as a two-phase process. 78/// Main entry point for completion. We run completion as a two-phase process.
72/// 79///
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index 1fa02c375..e892d4de8 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -9,7 +9,8 @@ pub(crate) mod type_alias;
9 9
10mod builder_ext; 10mod builder_ext;
11 11
12use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; 12use assists::utils::{ImportScope, MergeBehaviour};
13use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type};
13use ide_db::RootDatabase; 14use ide_db::RootDatabase;
14use syntax::TextRange; 15use syntax::TextRange;
15use test_utils::mark; 16use test_utils::mark;
@@ -42,7 +43,22 @@ pub(crate) fn render_resolution<'a>(
42 local_name: String, 43 local_name: String,
43 resolution: &ScopeDef, 44 resolution: &ScopeDef,
44) -> Option<CompletionItem> { 45) -> Option<CompletionItem> {
45 Render::new(ctx).render_resolution(local_name, resolution) 46 Render::new(ctx).render_resolution(local_name, None, resolution)
47}
48
49pub(crate) fn render_resolution_with_import<'a>(
50 ctx: RenderContext<'a>,
51 import: ModPath,
52 import_scope: ImportScope,
53 merge_behaviour: Option<MergeBehaviour>,
54 resolution: &ScopeDef,
55) -> Option<CompletionItem> {
56 let local_name = import.segments.last()?.to_string();
57 Render::new(ctx).render_resolution(
58 local_name,
59 Some((import, import_scope, merge_behaviour)),
60 resolution,
61 )
46} 62}
47 63
48/// Interface for data and methods required for items rendering. 64/// Interface for data and methods required for items rendering.
@@ -131,6 +147,7 @@ impl<'a> Render<'a> {
131 fn render_resolution( 147 fn render_resolution(
132 self, 148 self,
133 local_name: String, 149 local_name: String,
150 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
134 resolution: &ScopeDef, 151 resolution: &ScopeDef,
135 ) -> Option<CompletionItem> { 152 ) -> Option<CompletionItem> {
136 use hir::ModuleDef::*; 153 use hir::ModuleDef::*;
@@ -142,15 +159,15 @@ impl<'a> Render<'a> {
142 159
143 let kind = match resolution { 160 let kind = match resolution {
144 ScopeDef::ModuleDef(Function(func)) => { 161 ScopeDef::ModuleDef(Function(func)) => {
145 let item = render_fn(self.ctx, Some(local_name), *func); 162 let item = render_fn(self.ctx, import_data, Some(local_name), *func);
146 return Some(item); 163 return Some(item);
147 } 164 }
148 ScopeDef::ModuleDef(EnumVariant(var)) => { 165 ScopeDef::ModuleDef(EnumVariant(var)) => {
149 let item = render_enum_variant(self.ctx, Some(local_name), *var, None); 166 let item = render_enum_variant(self.ctx, import_data, Some(local_name), *var, None);
150 return Some(item); 167 return Some(item);
151 } 168 }
152 ScopeDef::MacroDef(mac) => { 169 ScopeDef::MacroDef(mac) => {
153 let item = render_macro(self.ctx, local_name, *mac); 170 let item = render_macro(self.ctx, import_data, local_name, *mac);
154 return item; 171 return item;
155 } 172 }
156 173
@@ -175,6 +192,7 @@ impl<'a> Render<'a> {
175 local_name, 192 local_name,
176 ) 193 )
177 .kind(CompletionItemKind::UnresolvedReference) 194 .kind(CompletionItemKind::UnresolvedReference)
195 .import_data(import_data)
178 .build(); 196 .build();
179 return Some(item); 197 return Some(item);
180 } 198 }
@@ -227,7 +245,12 @@ impl<'a> Render<'a> {
227 } 245 }
228 } 246 }
229 247
230 let item = item.kind(kind).set_documentation(docs).set_ref_match(ref_match).build(); 248 let item = item
249 .kind(kind)
250 .import_data(import_data)
251 .set_documentation(docs)
252 .set_ref_match(ref_match)
253 .build();
231 Some(item) 254 Some(item)
232 } 255 }
233 256
@@ -426,6 +449,28 @@ fn main() { let _: m::Spam = S<|> }
426 kind: Module, 449 kind: Module,
427 }, 450 },
428 CompletionItem { 451 CompletionItem {
452 label: "m::Spam",
453 source_range: 75..76,
454 text_edit: TextEdit {
455 indels: [
456 Indel {
457 insert: "use m::Spam;",
458 delete: 0..0,
459 },
460 Indel {
461 insert: "\n\n",
462 delete: 0..0,
463 },
464 Indel {
465 insert: "Spam",
466 delete: 75..76,
467 },
468 ],
469 },
470 kind: Enum,
471 lookup: "Spam",
472 },
473 CompletionItem {
429 label: "m::Spam::Foo", 474 label: "m::Spam::Foo",
430 source_range: 75..76, 475 source_range: 75..76,
431 delete: 75..76, 476 delete: 75..76,
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs
index fd412ed0e..6070e9b1d 100644
--- a/crates/completion/src/render/enum_variant.rs
+++ b/crates/completion/src/render/enum_variant.rs
@@ -1,5 +1,6 @@
1//! Renderer for `enum` variants. 1//! Renderer for `enum` variants.
2 2
3use assists::utils::{ImportScope, MergeBehaviour};
3use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; 4use hir::{HasAttrs, HirDisplay, ModPath, StructKind};
4use itertools::Itertools; 5use itertools::Itertools;
5use test_utils::mark; 6use test_utils::mark;
@@ -11,11 +12,12 @@ use crate::{
11 12
12pub(crate) fn render_enum_variant<'a>( 13pub(crate) fn render_enum_variant<'a>(
13 ctx: RenderContext<'a>, 14 ctx: RenderContext<'a>,
15 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
14 local_name: Option<String>, 16 local_name: Option<String>,
15 variant: hir::EnumVariant, 17 variant: hir::EnumVariant,
16 path: Option<ModPath>, 18 path: Option<ModPath>,
17) -> CompletionItem { 19) -> CompletionItem {
18 EnumVariantRender::new(ctx, local_name, variant, path).render() 20 EnumVariantRender::new(ctx, local_name, variant, path).render(import_data)
19} 21}
20 22
21#[derive(Debug)] 23#[derive(Debug)]
@@ -60,7 +62,10 @@ impl<'a> EnumVariantRender<'a> {
60 } 62 }
61 } 63 }
62 64
63 fn render(self) -> CompletionItem { 65 fn render(
66 self,
67 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
68 ) -> CompletionItem {
64 let mut builder = CompletionItem::new( 69 let mut builder = CompletionItem::new(
65 CompletionKind::Reference, 70 CompletionKind::Reference,
66 self.ctx.source_range(), 71 self.ctx.source_range(),
@@ -69,6 +74,7 @@ impl<'a> EnumVariantRender<'a> {
69 .kind(CompletionItemKind::EnumVariant) 74 .kind(CompletionItemKind::EnumVariant)
70 .set_documentation(self.variant.docs(self.ctx.db())) 75 .set_documentation(self.variant.docs(self.ctx.db()))
71 .set_deprecated(self.ctx.is_deprecated(self.variant)) 76 .set_deprecated(self.ctx.is_deprecated(self.variant))
77 .import_data(import_data)
72 .detail(self.detail()); 78 .detail(self.detail());
73 79
74 if self.variant_kind == StructKind::Tuple { 80 if self.variant_kind == StructKind::Tuple {
diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs
index 4fa6eafd7..9dd5cd18c 100644
--- a/crates/completion/src/render/function.rs
+++ b/crates/completion/src/render/function.rs
@@ -1,6 +1,7 @@
1//! Renderer for function calls. 1//! Renderer for function calls.
2 2
3use hir::{HasSource, Type}; 3use assists::utils::{ImportScope, MergeBehaviour};
4use hir::{HasSource, ModPath, Type};
4use syntax::{ast::Fn, display::function_declaration}; 5use syntax::{ast::Fn, display::function_declaration};
5 6
6use crate::{ 7use crate::{
@@ -10,10 +11,11 @@ use crate::{
10 11
11pub(crate) fn render_fn<'a>( 12pub(crate) fn render_fn<'a>(
12 ctx: RenderContext<'a>, 13 ctx: RenderContext<'a>,
14 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
13 local_name: Option<String>, 15 local_name: Option<String>,
14 fn_: hir::Function, 16 fn_: hir::Function,
15) -> CompletionItem { 17) -> CompletionItem {
16 FunctionRender::new(ctx, local_name, fn_).render() 18 FunctionRender::new(ctx, local_name, fn_).render(import_data)
17} 19}
18 20
19#[derive(Debug)] 21#[derive(Debug)]
@@ -36,7 +38,10 @@ impl<'a> FunctionRender<'a> {
36 FunctionRender { ctx, name, fn_, ast_node } 38 FunctionRender { ctx, name, fn_, ast_node }
37 } 39 }
38 40
39 fn render(self) -> CompletionItem { 41 fn render(
42 self,
43 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
44 ) -> CompletionItem {
40 let params = self.params(); 45 let params = self.params();
41 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) 46 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
42 .kind(self.kind()) 47 .kind(self.kind())
@@ -44,6 +49,7 @@ impl<'a> FunctionRender<'a> {
44 .set_deprecated(self.ctx.is_deprecated(self.fn_)) 49 .set_deprecated(self.ctx.is_deprecated(self.fn_))
45 .detail(self.detail()) 50 .detail(self.detail())
46 .add_call_parens(self.ctx.completion, self.name, params) 51 .add_call_parens(self.ctx.completion, self.name, params)
52 .import_data(import_data)
47 .build() 53 .build()
48 } 54 }
49 55
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs
index 96be59cc3..fead59e41 100644
--- a/crates/completion/src/render/macro_.rs
+++ b/crates/completion/src/render/macro_.rs
@@ -1,6 +1,7 @@
1//! Renderer for macro invocations. 1//! Renderer for macro invocations.
2 2
3use hir::{Documentation, HasSource}; 3use assists::utils::{ImportScope, MergeBehaviour};
4use hir::{Documentation, HasSource, ModPath};
4use syntax::display::macro_label; 5use syntax::display::macro_label;
5use test_utils::mark; 6use test_utils::mark;
6 7
@@ -11,10 +12,11 @@ use crate::{
11 12
12pub(crate) fn render_macro<'a>( 13pub(crate) fn render_macro<'a>(
13 ctx: RenderContext<'a>, 14 ctx: RenderContext<'a>,
15 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
14 name: String, 16 name: String,
15 macro_: hir::MacroDef, 17 macro_: hir::MacroDef,
16) -> Option<CompletionItem> { 18) -> Option<CompletionItem> {
17 MacroRender::new(ctx, name, macro_).render() 19 MacroRender::new(ctx, name, macro_).render(import_data)
18} 20}
19 21
20#[derive(Debug)] 22#[derive(Debug)]
@@ -36,7 +38,10 @@ impl<'a> MacroRender<'a> {
36 MacroRender { ctx, name, macro_, docs, bra, ket } 38 MacroRender { ctx, name, macro_, docs, bra, ket }
37 } 39 }
38 40
39 fn render(&self) -> Option<CompletionItem> { 41 fn render(
42 &self,
43 import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
44 ) -> Option<CompletionItem> {
40 // FIXME: Currently proc-macro do not have ast-node, 45 // FIXME: Currently proc-macro do not have ast-node,
41 // such that it does not have source 46 // such that it does not have source
42 if self.macro_.is_proc_macro() { 47 if self.macro_.is_proc_macro() {
@@ -48,6 +53,7 @@ impl<'a> MacroRender<'a> {
48 .kind(CompletionItemKind::Macro) 53 .kind(CompletionItemKind::Macro)
49 .set_documentation(self.docs.clone()) 54 .set_documentation(self.docs.clone())
50 .set_deprecated(self.ctx.is_deprecated(self.macro_)) 55 .set_deprecated(self.ctx.is_deprecated(self.macro_))
56 .import_data(import_data)
51 .detail(self.detail()); 57 .detail(self.detail());
52 58
53 let needs_bang = self.needs_bang(); 59 let needs_bang = self.needs_bang();
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 30a5e4580..37ed092ad 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -110,15 +110,9 @@ impl Crate {
110 pub fn query_external_importables( 110 pub fn query_external_importables(
111 self, 111 self,
112 db: &dyn DefDatabase, 112 db: &dyn DefDatabase,
113 query: &str, 113 query: import_map::Query,
114 ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 114 ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
115 import_map::search_dependencies( 115 import_map::search_dependencies(db, self.into(), query).into_iter().map(|item| match item {
116 db,
117 self.into(),
118 import_map::Query::new(query).anchor_end().case_sensitive().limit(40),
119 )
120 .into_iter()
121 .map(|item| match item {
122 ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()), 116 ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()),
123 ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()), 117 ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()),
124 }) 118 })
diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs
index 07333c453..8c767b249 100644
--- a/crates/hir/src/db.rs
+++ b/crates/hir/src/db.rs
@@ -11,7 +11,7 @@ pub use hir_def::db::{
11}; 11};
12pub use hir_expand::db::{ 12pub use hir_expand::db::{
13 AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, 13 AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
14 MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery, 14 MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery,
15}; 15};
16pub use hir_ty::db::*; 16pub use hir_ty::db::*;
17 17
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index c18c1c587..d9ad8db6f 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -1,6 +1,8 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2pub use hir_def::diagnostics::{InactiveCode, UnresolvedModule}; 2pub use hir_def::diagnostics::{InactiveCode, UnresolvedModule};
3pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder}; 3pub use hir_expand::diagnostics::{
4 Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
5};
4pub use hir_ty::diagnostics::{ 6pub use hir_ty::diagnostics::{
5 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, 7 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr,
6 NoSuchField, 8 NoSuchField,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0d184379f..ed110329d 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -49,6 +49,7 @@ pub use hir_def::{
49 builtin_type::BuiltinType, 49 builtin_type::BuiltinType,
50 docs::Documentation, 50 docs::Documentation,
51 find_path::PrefixKind, 51 find_path::PrefixKind,
52 import_map,
52 item_scope::ItemInNs, 53 item_scope::ItemInNs,
53 nameres::ModuleSource, 54 nameres::ModuleSource,
54 path::{ModPath, PathKind}, 55 path::{ModPath, PathKind},
@@ -56,8 +57,8 @@ pub use hir_def::{
56 visibility::Visibility, 57 visibility::Visibility,
57}; 58};
58pub use hir_expand::{ 59pub use hir_expand::{
59 name::known, name::AsName, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, 60 db::MacroResult, name::known, name::AsName, name::Name, HirFileId, InFile, MacroCallId,
60 /* FIXME */ MacroDefId, MacroFile, Origin, 61 MacroCallLoc, /* FIXME */ MacroDefId, MacroFile, Origin,
61}; 62};
62pub use hir_ty::display::HirDisplay; 63pub use hir_ty::display::HirDisplay;
63 64
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index ade57ac1b..a9099eb22 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -13,6 +13,19 @@ use crate::{
13 MacroFile, ProcMacroExpander, 13 MacroFile, ProcMacroExpander,
14}; 14};
15 15
16/// A result of some macro expansion.
17#[derive(Debug, Clone, Eq, PartialEq)]
18pub struct MacroResult<T> {
19 /// The result of the expansion. Might be `None` when error recovery was impossible and no
20 /// usable result was produced.
21 pub value: Option<T>,
22
23 /// The error that occurred during expansion or processing.
24 ///
25 /// Since we do error recovery, getting an error here does not mean that `value` will be absent.
26 pub error: Option<String>,
27}
28
16#[derive(Debug, Clone, Eq, PartialEq)] 29#[derive(Debug, Clone, Eq, PartialEq)]
17pub enum TokenExpander { 30pub enum TokenExpander {
18 MacroRules(mbe::MacroRules), 31 MacroRules(mbe::MacroRules),
@@ -75,9 +88,11 @@ pub trait AstDatabase: SourceDatabase {
75 #[salsa::transparent] 88 #[salsa::transparent]
76 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>; 89 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>;
77 fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>; 90 fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>;
78 fn parse_macro(&self, macro_file: MacroFile) 91 fn parse_macro_expansion(
79 -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>; 92 &self,
80 fn macro_expand(&self, macro_call: MacroCallId) -> (Option<Arc<tt::Subtree>>, Option<String>); 93 macro_file: MacroFile,
94 ) -> MacroResult<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>;
95 fn macro_expand(&self, macro_call: MacroCallId) -> MacroResult<Arc<tt::Subtree>>;
81 96
82 #[salsa::interned] 97 #[salsa::interned]
83 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; 98 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
@@ -85,6 +100,20 @@ pub trait AstDatabase: SourceDatabase {
85 fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>; 100 fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>;
86} 101}
87 102
103impl<T> MacroResult<T> {
104 fn error(message: String) -> Self {
105 Self { value: None, error: Some(message) }
106 }
107
108 fn map<U>(self, f: impl FnOnce(T) -> U) -> MacroResult<U> {
109 MacroResult { value: self.value.map(f), error: self.error }
110 }
111
112 fn drop_value<U>(self) -> MacroResult<U> {
113 MacroResult { value: None, error: self.error }
114 }
115}
116
88/// This expands the given macro call, but with different arguments. This is 117/// This expands the given macro call, but with different arguments. This is
89/// used for completion, where we want to see what 'would happen' if we insert a 118/// used for completion, where we want to see what 'would happen' if we insert a
90/// token. The `token_to_map` mapped down into the expansion, with the mapped 119/// token. The `token_to_map` mapped down into the expansion, with the mapped
@@ -102,23 +131,20 @@ pub fn expand_hypothetical(
102 let token_id = tmap_1.token_by_range(range)?; 131 let token_id = tmap_1.token_by_range(range)?;
103 let macro_def = expander(db, actual_macro_call)?; 132 let macro_def = expander(db, actual_macro_call)?;
104 let (node, tmap_2) = 133 let (node, tmap_2) =
105 parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1))))?; 134 parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1)))).value?;
106 let token_id = macro_def.0.map_id_down(token_id); 135 let token_id = macro_def.0.map_id_down(token_id);
107 let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?; 136 let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?;
108 let token = syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?; 137 let token = syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?;
109 Some((node.syntax_node(), token)) 138 Some((node.syntax_node(), token))
110} 139}
111 140
112pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { 141fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
113 let map = 142 let map =
114 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); 143 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it));
115 Arc::new(map) 144 Arc::new(map)
116} 145}
117 146
118pub(crate) fn macro_def( 147fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
119 db: &dyn AstDatabase,
120 id: MacroDefId,
121) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
122 match id.kind { 148 match id.kind {
123 MacroDefKind::Declarative => { 149 MacroDefKind::Declarative => {
124 let macro_call = id.ast_id?.to_node(db); 150 let macro_call = id.ast_id?.to_node(db);
@@ -149,7 +175,7 @@ pub(crate) fn macro_def(
149 } 175 }
150} 176}
151 177
152pub(crate) fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> { 178fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> {
153 let id = match id { 179 let id = match id {
154 MacroCallId::LazyMacro(id) => id, 180 MacroCallId::LazyMacro(id) => id,
155 MacroCallId::EagerMacro(_id) => { 181 MacroCallId::EagerMacro(_id) => {
@@ -162,19 +188,13 @@ pub(crate) fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<Gr
162 Some(arg.green().clone()) 188 Some(arg.green().clone())
163} 189}
164 190
165pub(crate) fn macro_arg( 191fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> {
166 db: &dyn AstDatabase,
167 id: MacroCallId,
168) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> {
169 let arg = db.macro_arg_text(id)?; 192 let arg = db.macro_arg_text(id)?;
170 let (tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg))?; 193 let (tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg))?;
171 Some(Arc::new((tt, tmap))) 194 Some(Arc::new((tt, tmap)))
172} 195}
173 196
174pub(crate) fn macro_expand( 197fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> MacroResult<Arc<tt::Subtree>> {
175 db: &dyn AstDatabase,
176 id: MacroCallId,
177) -> (Option<Arc<tt::Subtree>>, Option<String>) {
178 macro_expand_with_arg(db, id, None) 198 macro_expand_with_arg(db, id, None)
179} 199}
180 200
@@ -195,17 +215,19 @@ fn macro_expand_with_arg(
195 db: &dyn AstDatabase, 215 db: &dyn AstDatabase,
196 id: MacroCallId, 216 id: MacroCallId,
197 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, 217 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
198) -> (Option<Arc<tt::Subtree>>, Option<String>) { 218) -> MacroResult<Arc<tt::Subtree>> {
199 let lazy_id = match id { 219 let lazy_id = match id {
200 MacroCallId::LazyMacro(id) => id, 220 MacroCallId::LazyMacro(id) => id,
201 MacroCallId::EagerMacro(id) => { 221 MacroCallId::EagerMacro(id) => {
202 if arg.is_some() { 222 if arg.is_some() {
203 return ( 223 return MacroResult::error(
204 None, 224 "hypothetical macro expansion not implemented for eager macro".to_owned(),
205 Some("hypothetical macro expansion not implemented for eager macro".to_owned()),
206 ); 225 );
207 } else { 226 } else {
208 return (Some(db.lookup_intern_eager_expansion(id).subtree), None); 227 return MacroResult {
228 value: Some(db.lookup_intern_eager_expansion(id).subtree),
229 error: None,
230 };
209 } 231 }
210 } 232 }
211 }; 233 };
@@ -213,23 +235,24 @@ fn macro_expand_with_arg(
213 let loc = db.lookup_intern_macro(lazy_id); 235 let loc = db.lookup_intern_macro(lazy_id);
214 let macro_arg = match arg.or_else(|| db.macro_arg(id)) { 236 let macro_arg = match arg.or_else(|| db.macro_arg(id)) {
215 Some(it) => it, 237 Some(it) => it,
216 None => return (None, Some("Fail to args in to tt::TokenTree".into())), 238 None => return MacroResult::error("Fail to args in to tt::TokenTree".into()),
217 }; 239 };
218 240
219 let macro_rules = match db.macro_def(loc.def) { 241 let macro_rules = match db.macro_def(loc.def) {
220 Some(it) => it, 242 Some(it) => it,
221 None => return (None, Some("Fail to find macro definition".into())), 243 None => return MacroResult::error("Fail to find macro definition".into()),
222 }; 244 };
223 let ExpandResult(tt, err) = macro_rules.0.expand(db, lazy_id, &macro_arg.0); 245 let ExpandResult(tt, err) = macro_rules.0.expand(db, lazy_id, &macro_arg.0);
224 // Set a hard limit for the expanded tt 246 // Set a hard limit for the expanded tt
225 let count = tt.count(); 247 let count = tt.count();
226 if count > 262144 { 248 if count > 262144 {
227 return (None, Some(format!("Total tokens count exceed limit : count = {}", count))); 249 return MacroResult::error(format!("Total tokens count exceed limit : count = {}", count));
228 } 250 }
229 (Some(Arc::new(tt)), err.map(|e| format!("{:?}", e))) 251
252 MacroResult { value: Some(Arc::new(tt)), error: err.map(|e| format!("{:?}", e)) }
230} 253}
231 254
232pub(crate) fn expand_proc_macro( 255fn expand_proc_macro(
233 db: &dyn AstDatabase, 256 db: &dyn AstDatabase,
234 id: MacroCallId, 257 id: MacroCallId,
235) -> Result<tt::Subtree, mbe::ExpandError> { 258) -> Result<tt::Subtree, mbe::ExpandError> {
@@ -256,36 +279,36 @@ pub(crate) fn expand_proc_macro(
256 expander.expand(db, lazy_id, &macro_arg.0) 279 expander.expand(db, lazy_id, &macro_arg.0)
257} 280}
258 281
259pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> { 282fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> {
260 match file_id.0 { 283 match file_id.0 {
261 HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), 284 HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()),
262 HirFileIdRepr::MacroFile(macro_file) => { 285 HirFileIdRepr::MacroFile(macro_file) => {
263 db.parse_macro(macro_file).map(|(it, _)| it.syntax_node()) 286 db.parse_macro_expansion(macro_file).map(|(it, _)| it.syntax_node()).value
264 } 287 }
265 } 288 }
266} 289}
267 290
268pub(crate) fn parse_macro( 291fn parse_macro_expansion(
269 db: &dyn AstDatabase, 292 db: &dyn AstDatabase,
270 macro_file: MacroFile, 293 macro_file: MacroFile,
271) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { 294) -> MacroResult<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
272 parse_macro_with_arg(db, macro_file, None) 295 parse_macro_with_arg(db, macro_file, None)
273} 296}
274 297
275pub fn parse_macro_with_arg( 298fn parse_macro_with_arg(
276 db: &dyn AstDatabase, 299 db: &dyn AstDatabase,
277 macro_file: MacroFile, 300 macro_file: MacroFile,
278 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, 301 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
279) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { 302) -> MacroResult<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
280 let _p = profile::span("parse_macro_query"); 303 let _p = profile::span("parse_macro_query");
281 304
282 let macro_call_id = macro_file.macro_call_id; 305 let macro_call_id = macro_file.macro_call_id;
283 let (tt, err) = if let Some(arg) = arg { 306 let result = if let Some(arg) = arg {
284 macro_expand_with_arg(db, macro_call_id, Some(arg)) 307 macro_expand_with_arg(db, macro_call_id, Some(arg))
285 } else { 308 } else {
286 db.macro_expand(macro_call_id) 309 db.macro_expand(macro_call_id)
287 }; 310 };
288 if let Some(err) = &err { 311 if let Some(err) = &result.error {
289 // Note: 312 // Note:
290 // The final goal we would like to make all parse_macro success, 313 // The final goal we would like to make all parse_macro success,
291 // such that the following log will not call anyway. 314 // such that the following log will not call anyway.
@@ -313,30 +336,40 @@ pub fn parse_macro_with_arg(
313 log::warn!("fail on macro_parse: (reason: {})", err); 336 log::warn!("fail on macro_parse: (reason: {})", err);
314 } 337 }
315 } 338 }
339 }
340 let tt = match result.value {
341 Some(tt) => tt,
342 None => return result.drop_value(),
316 }; 343 };
317 let tt = tt?;
318 344
319 let fragment_kind = to_fragment_kind(db, macro_call_id); 345 let fragment_kind = to_fragment_kind(db, macro_call_id);
320 346
321 let (parse, rev_token_map) = mbe::token_tree_to_syntax_node(&tt, fragment_kind).ok()?; 347 let (parse, rev_token_map) = match mbe::token_tree_to_syntax_node(&tt, fragment_kind) {
348 Ok(it) => it,
349 Err(err) => {
350 return MacroResult::error(format!("{:?}", err));
351 }
352 };
322 353
323 if err.is_none() { 354 match result.error {
324 Some((parse, Arc::new(rev_token_map))) 355 Some(error) => {
325 } else { 356 // Safety check for recursive identity macro.
326 // FIXME: 357 let node = parse.syntax_node();
327 // In future, we should propagate the actual error with recovery information 358 let file: HirFileId = macro_file.into();
328 // instead of ignore the error here. 359 let call_node = match file.call_node(db) {
329 360 Some(it) => it,
330 // Safe check for recurisve identity macro 361 None => {
331 let node = parse.syntax_node(); 362 return MacroResult::error(error);
332 let file: HirFileId = macro_file.into(); 363 }
333 let call_node = file.call_node(db)?; 364 };
334 365
335 if !diff(&node, &call_node.value).is_empty() { 366 if !diff(&node, &call_node.value).is_empty() {
336 Some((parse, Arc::new(rev_token_map))) 367 MacroResult { value: Some((parse, Arc::new(rev_token_map))), error: Some(error) }
337 } else { 368 } else {
338 None 369 return MacroResult::error(error);
370 }
339 } 371 }
372 None => MacroResult { value: Some((parse, Arc::new(rev_token_map))), error: None },
340 } 373 }
341} 374}
342 375
diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_expand/src/diagnostics.rs
index 78ccc212c..1043c6aeb 100644
--- a/crates/hir_expand/src/diagnostics.rs
+++ b/crates/hir_expand/src/diagnostics.rs
@@ -20,7 +20,7 @@ use syntax::SyntaxNodePtr;
20 20
21use crate::InFile; 21use crate::InFile;
22 22
23#[derive(Copy, Clone, PartialEq)] 23#[derive(Copy, Clone, Debug, PartialEq)]
24pub struct DiagnosticCode(pub &'static str); 24pub struct DiagnosticCode(pub &'static str);
25 25
26impl DiagnosticCode { 26impl DiagnosticCode {
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 17f1178ed..83e09738b 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -144,7 +144,7 @@ impl HirFileId {
144 let def_tt = loc.def.ast_id?.to_node(db).token_tree()?; 144 let def_tt = loc.def.ast_id?.to_node(db).token_tree()?;
145 145
146 let macro_def = db.macro_def(loc.def)?; 146 let macro_def = db.macro_def(loc.def)?;
147 let (parse, exp_map) = db.parse_macro(macro_file)?; 147 let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?;
148 let macro_arg = db.macro_arg(macro_file.macro_call_id)?; 148 let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
149 149
150 Some(ExpansionInfo { 150 Some(ExpansionInfo {
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index fdc65a5c3..cf5c38a23 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -35,3 +35,4 @@ expect-test = "1.0"
35tracing = "0.1" 35tracing = "0.1"
36tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] } 36tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] }
37tracing-tree = { version = "0.1.4" } 37tracing-tree = { version = "0.1.4" }
38once_cell = { version = "1.5.0", features = ["unstable"] }
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index 5bd03f2ac..62c329731 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -216,14 +216,14 @@
216//! U(P, p) := U(P, (r_1, p_2, .., p_n)) 216//! U(P, p) := U(P, (r_1, p_2, .., p_n))
217//! || U(P, (r_2, p_2, .., p_n)) 217//! || U(P, (r_2, p_2, .., p_n))
218//! ``` 218//! ```
219use std::sync::Arc; 219use std::{iter, sync::Arc};
220 220
221use arena::Idx; 221use arena::Idx;
222use hir_def::{ 222use hir_def::{
223 adt::VariantData, 223 adt::VariantData,
224 body::Body, 224 body::Body,
225 expr::{Expr, Literal, Pat, PatId}, 225 expr::{Expr, Literal, Pat, PatId},
226 AdtId, EnumVariantId, VariantId, 226 AdtId, EnumVariantId, StructId, VariantId,
227}; 227};
228use smallvec::{smallvec, SmallVec}; 228use smallvec::{smallvec, SmallVec};
229 229
@@ -366,16 +366,17 @@ impl PatStack {
366 366
367 let head_pat = head.as_pat(cx); 367 let head_pat = head.as_pat(cx);
368 let result = match (head_pat, constructor) { 368 let result = match (head_pat, constructor) {
369 (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { 369 (Pat::Tuple { args: pat_ids, ellipsis }, &Constructor::Tuple { arity }) => {
370 if ellipsis.is_some() { 370 if let Some(ellipsis) = ellipsis {
371 // If there are ellipsis here, we should add the correct number of 371 let (pre, post) = pat_ids.split_at(ellipsis);
372 // Pat::Wild patterns to `pat_ids`. We should be able to use the 372 let n_wild_pats = arity.saturating_sub(pat_ids.len());
373 // constructors arity for this, but at the time of writing we aren't 373 let pre_iter = pre.iter().map(Into::into);
374 // correctly calculating this arity when ellipsis are present. 374 let wildcards = iter::repeat(PatIdOrWild::Wild).take(n_wild_pats);
375 return Err(MatchCheckErr::NotImplemented); 375 let post_iter = post.iter().map(Into::into);
376 Some(self.replace_head_with(pre_iter.chain(wildcards).chain(post_iter)))
377 } else {
378 Some(self.replace_head_with(pat_ids.iter()))
376 } 379 }
377
378 Some(self.replace_head_with(pat_ids.iter()))
379 } 380 }
380 (Pat::Lit(lit_expr), Constructor::Bool(constructor_val)) => { 381 (Pat::Lit(lit_expr), Constructor::Bool(constructor_val)) => {
381 match cx.body.exprs[lit_expr] { 382 match cx.body.exprs[lit_expr] {
@@ -390,21 +391,28 @@ impl PatStack {
390 } 391 }
391 } 392 }
392 (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), 393 (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?),
393 (Pat::Path(_), Constructor::Enum(constructor)) => { 394 (Pat::Path(_), constructor) => {
394 // unit enum variants become `Pat::Path` 395 // unit enum variants become `Pat::Path`
395 let pat_id = head.as_id().expect("we know this isn't a wild"); 396 let pat_id = head.as_id().expect("we know this isn't a wild");
396 if !enum_variant_matches(cx, pat_id, *constructor) { 397 let variant_id: VariantId = match constructor {
398 &Constructor::Enum(e) => e.into(),
399 &Constructor::Struct(s) => s.into(),
400 _ => return Err(MatchCheckErr::NotImplemented),
401 };
402 if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) {
397 None 403 None
398 } else { 404 } else {
399 Some(self.to_tail()) 405 Some(self.to_tail())
400 } 406 }
401 } 407 }
402 ( 408 (Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, constructor) => {
403 Pat::TupleStruct { args: ref pat_ids, ellipsis, .. },
404 Constructor::Enum(enum_constructor),
405 ) => {
406 let pat_id = head.as_id().expect("we know this isn't a wild"); 409 let pat_id = head.as_id().expect("we know this isn't a wild");
407 if !enum_variant_matches(cx, pat_id, *enum_constructor) { 410 let variant_id: VariantId = match constructor {
411 &Constructor::Enum(e) => e.into(),
412 &Constructor::Struct(s) => s.into(),
413 _ => return Err(MatchCheckErr::MalformedMatchArm),
414 };
415 if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) {
408 None 416 None
409 } else { 417 } else {
410 let constructor_arity = constructor.arity(cx)?; 418 let constructor_arity = constructor.arity(cx)?;
@@ -442,12 +450,22 @@ impl PatStack {
442 } 450 }
443 } 451 }
444 } 452 }
445 (Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => { 453 (Pat::Record { args: ref arg_patterns, .. }, constructor) => {
446 let pat_id = head.as_id().expect("we know this isn't a wild"); 454 let pat_id = head.as_id().expect("we know this isn't a wild");
447 if !enum_variant_matches(cx, pat_id, *e) { 455 let (variant_id, variant_data) = match constructor {
456 &Constructor::Enum(e) => (
457 e.into(),
458 cx.db.enum_data(e.parent).variants[e.local_id].variant_data.clone(),
459 ),
460 &Constructor::Struct(s) => {
461 (s.into(), cx.db.struct_data(s).variant_data.clone())
462 }
463 _ => return Err(MatchCheckErr::MalformedMatchArm),
464 };
465 if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) {
448 None 466 None
449 } else { 467 } else {
450 match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() { 468 match variant_data.as_ref() {
451 VariantData::Record(struct_field_arena) => { 469 VariantData::Record(struct_field_arena) => {
452 // Here we treat any missing fields in the record as the wild pattern, as 470 // Here we treat any missing fields in the record as the wild pattern, as
453 // if the record has ellipsis. We want to do this here even if the 471 // if the record has ellipsis. We want to do this here even if the
@@ -726,6 +744,7 @@ enum Constructor {
726 Bool(bool), 744 Bool(bool),
727 Tuple { arity: usize }, 745 Tuple { arity: usize },
728 Enum(EnumVariantId), 746 Enum(EnumVariantId),
747 Struct(StructId),
729} 748}
730 749
731impl Constructor { 750impl Constructor {
@@ -740,6 +759,11 @@ impl Constructor {
740 VariantData::Unit => 0, 759 VariantData::Unit => 0,
741 } 760 }
742 } 761 }
762 &Constructor::Struct(s) => match cx.db.struct_data(s).variant_data.as_ref() {
763 VariantData::Tuple(struct_field_data) => struct_field_data.len(),
764 VariantData::Record(struct_field_data) => struct_field_data.len(),
765 VariantData::Unit => 0,
766 },
743 }; 767 };
744 768
745 Ok(arity) 769 Ok(arity)
@@ -748,7 +772,7 @@ impl Constructor {
748 fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> { 772 fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> {
749 match self { 773 match self {
750 Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)], 774 Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)],
751 Constructor::Tuple { .. } => vec![*self], 775 Constructor::Tuple { .. } | Constructor::Struct(_) => vec![*self],
752 Constructor::Enum(e) => cx 776 Constructor::Enum(e) => cx
753 .db 777 .db
754 .enum_data(e.parent) 778 .enum_data(e.parent)
@@ -767,10 +791,11 @@ impl Constructor {
767fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> { 791fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> {
768 let res = match pat.as_pat(cx) { 792 let res = match pat.as_pat(cx) {
769 Pat::Wild => None, 793 Pat::Wild => None,
770 // FIXME somehow create the Tuple constructor with the proper arity. If there are 794 Pat::Tuple { .. } => {
771 // ellipsis, the arity is not equal to the number of patterns. 795 let pat_id = pat.as_id().expect("we already know this pattern is not a wild");
772 Pat::Tuple { args: pats, ellipsis } if ellipsis.is_none() => { 796 Some(Constructor::Tuple {
773 Some(Constructor::Tuple { arity: pats.len() }) 797 arity: cx.infer.type_of_pat[pat_id].as_tuple().ok_or(MatchCheckErr::Unknown)?.len(),
798 })
774 } 799 }
775 Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] { 800 Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
776 Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)), 801 Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
@@ -784,6 +809,7 @@ fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Opt
784 VariantId::EnumVariantId(enum_variant_id) => { 809 VariantId::EnumVariantId(enum_variant_id) => {
785 Some(Constructor::Enum(enum_variant_id)) 810 Some(Constructor::Enum(enum_variant_id))
786 } 811 }
812 VariantId::StructId(struct_id) => Some(Constructor::Struct(struct_id)),
787 _ => return Err(MatchCheckErr::NotImplemented), 813 _ => return Err(MatchCheckErr::NotImplemented),
788 } 814 }
789 } 815 }
@@ -828,13 +854,13 @@ fn all_constructors_covered(
828 854
829 false 855 false
830 }), 856 }),
857 &Constructor::Struct(s) => used_constructors.iter().any(|constructor| match constructor {
858 &Constructor::Struct(sid) => sid == s,
859 _ => false,
860 }),
831 } 861 }
832} 862}
833 863
834fn enum_variant_matches(cx: &MatchCheckCtx, pat_id: PatId, enum_variant_id: EnumVariantId) -> bool {
835 Some(enum_variant_id.into()) == cx.infer.variant_resolution_for_pat(pat_id)
836}
837
838#[cfg(test)] 864#[cfg(test)]
839mod tests { 865mod tests {
840 use crate::diagnostics::tests::check_diagnostics; 866 use crate::diagnostics::tests::check_diagnostics;
@@ -846,8 +872,8 @@ mod tests {
846fn main() { 872fn main() {
847 match () { } 873 match () { }
848 //^^ Missing match arm 874 //^^ Missing match arm
849 match (()) { } 875 match (()) { }
850 //^^^^ Missing match arm 876 //^^^^ Missing match arm
851 877
852 match () { _ => (), } 878 match () { _ => (), }
853 match () { () => (), } 879 match () { () => (), }
@@ -1352,6 +1378,123 @@ fn main() {
1352 ); 1378 );
1353 } 1379 }
1354 1380
1381 #[test]
1382 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
1383 check_diagnostics(
1384 r#"
1385fn main() {
1386 match (false, true, false) {
1387 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1388 (false, ..) => (),
1389 }
1390}"#,
1391 );
1392 }
1393
1394 #[test]
1395 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
1396 check_diagnostics(
1397 r#"
1398fn main() {
1399 match (false, true, false) {
1400 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1401 (.., false) => (),
1402 }
1403}"#,
1404 );
1405 }
1406
1407 #[test]
1408 fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() {
1409 check_diagnostics(
1410 r#"
1411fn main() {
1412 match (false, true, false) {
1413 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1414 (true, .., false) => (),
1415 }
1416}"#,
1417 );
1418 }
1419
1420 #[test]
1421 fn record_struct() {
1422 check_diagnostics(
1423 r#"struct Foo { a: bool }
1424fn main(f: Foo) {
1425 match f {}
1426 //^ Missing match arm
1427 match f { Foo { a: true } => () }
1428 //^ Missing match arm
1429 match &f { Foo { a: true } => () }
1430 //^^ Missing match arm
1431 match f { Foo { a: _ } => () }
1432 match f {
1433 Foo { a: true } => (),
1434 Foo { a: false } => (),
1435 }
1436 match &f {
1437 Foo { a: true } => (),
1438 Foo { a: false } => (),
1439 }
1440}
1441"#,
1442 );
1443 }
1444
1445 #[test]
1446 fn tuple_struct() {
1447 check_diagnostics(
1448 r#"struct Foo(bool);
1449fn main(f: Foo) {
1450 match f {}
1451 //^ Missing match arm
1452 match f { Foo(true) => () }
1453 //^ Missing match arm
1454 match f {
1455 Foo(true) => (),
1456 Foo(false) => (),
1457 }
1458}
1459"#,
1460 );
1461 }
1462
1463 #[test]
1464 fn unit_struct() {
1465 check_diagnostics(
1466 r#"struct Foo;
1467fn main(f: Foo) {
1468 match f {}
1469 //^ Missing match arm
1470 match f { Foo => () }
1471}
1472"#,
1473 );
1474 }
1475
1476 #[test]
1477 fn record_struct_ellipsis() {
1478 check_diagnostics(
1479 r#"struct Foo { foo: bool, bar: bool }
1480fn main(f: Foo) {
1481 match f { Foo { foo: true, .. } => () }
1482 //^ Missing match arm
1483 match f {
1484 //^ Missing match arm
1485 Foo { foo: true, .. } => (),
1486 Foo { bar: false, .. } => ()
1487 }
1488 match f { Foo { .. } => () }
1489 match f {
1490 Foo { foo: true, .. } => (),
1491 Foo { foo: false, .. } => ()
1492 }
1493}
1494"#,
1495 );
1496 }
1497
1355 mod false_negatives { 1498 mod false_negatives {
1356 //! The implementation of match checking here is a work in progress. As we roll this out, we 1499 //! The implementation of match checking here is a work in progress. As we roll this out, we
1357 //! prefer false negatives to false positives (ideally there would be no false positives). This 1500 //! prefer false negatives to false positives (ideally there would be no false positives). This
@@ -1393,46 +1536,5 @@ fn main() {
1393"#, 1536"#,
1394 ); 1537 );
1395 } 1538 }
1396
1397 #[test]
1398 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
1399 // We don't currently handle tuple patterns with ellipsis.
1400 check_diagnostics(
1401 r#"
1402fn main() {
1403 match (false, true, false) {
1404 (false, ..) => (),
1405 }
1406}
1407"#,
1408 );
1409 }
1410
1411 #[test]
1412 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
1413 // We don't currently handle tuple patterns with ellipsis.
1414 check_diagnostics(
1415 r#"
1416fn main() {
1417 match (false, true, false) {
1418 (.., false) => (),
1419 }
1420}
1421"#,
1422 );
1423 }
1424
1425 #[test]
1426 fn struct_missing_arm() {
1427 // We don't currently handle structs.
1428 check_diagnostics(
1429 r#"
1430struct Foo { a: bool }
1431fn main(f: Foo) {
1432 match f { Foo { a: true } => () }
1433}
1434"#,
1435 );
1436 }
1437 } 1539 }
1438} 1540}
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index cde2ab82b..b70ec55eb 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -23,6 +23,7 @@ impl<'a> InferenceContext<'a> {
23 expected: &Ty, 23 expected: &Ty,
24 default_bm: BindingMode, 24 default_bm: BindingMode,
25 id: PatId, 25 id: PatId,
26 ellipsis: Option<usize>,
26 ) -> Ty { 27 ) -> Ty {
27 let (ty, def) = self.resolve_variant(path); 28 let (ty, def) = self.resolve_variant(path);
28 let var_data = def.map(|it| variant_data(self.db.upcast(), it)); 29 let var_data = def.map(|it| variant_data(self.db.upcast(), it));
@@ -34,8 +35,15 @@ impl<'a> InferenceContext<'a> {
34 let substs = ty.substs().unwrap_or_else(Substs::empty); 35 let substs = ty.substs().unwrap_or_else(Substs::empty);
35 36
36 let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); 37 let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
38 let (pre, post) = match ellipsis {
39 Some(idx) => subpats.split_at(idx),
40 None => (&subpats[..], &[][..]),
41 };
42 let post_idx_offset = field_tys.iter().count() - post.len();
37 43
38 for (i, &subpat) in subpats.iter().enumerate() { 44 let pre_iter = pre.iter().enumerate();
45 let post_iter = (post_idx_offset..).zip(post.iter());
46 for (i, &subpat) in pre_iter.chain(post_iter) {
39 let expected_ty = var_data 47 let expected_ty = var_data
40 .as_ref() 48 .as_ref()
41 .and_then(|d| d.field(&Name::new_tuple_field(i))) 49 .and_then(|d| d.field(&Name::new_tuple_field(i)))
@@ -111,20 +119,29 @@ impl<'a> InferenceContext<'a> {
111 let expected = expected; 119 let expected = expected;
112 120
113 let ty = match &body[pat] { 121 let ty = match &body[pat] {
114 Pat::Tuple { ref args, .. } => { 122 &Pat::Tuple { ref args, ellipsis } => {
115 let expectations = match expected.as_tuple() { 123 let expectations = match expected.as_tuple() {
116 Some(parameters) => &*parameters.0, 124 Some(parameters) => &*parameters.0,
117 _ => &[], 125 _ => &[],
118 }; 126 };
119 let expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown));
120 127
121 let inner_tys = args 128 let (pre, post) = match ellipsis {
122 .iter() 129 Some(idx) => args.split_at(idx),
123 .zip(expectations_iter) 130 None => (&args[..], &[][..]),
124 .map(|(&pat, ty)| self.infer_pat(pat, ty, default_bm)) 131 };
125 .collect(); 132 let n_uncovered_patterns = expectations.len().saturating_sub(args.len());
133 let mut expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown));
134 let mut infer_pat = |(&pat, ty)| self.infer_pat(pat, ty, default_bm);
135
136 let mut inner_tys = Vec::with_capacity(n_uncovered_patterns + args.len());
137 inner_tys.extend(pre.iter().zip(expectations_iter.by_ref()).map(&mut infer_pat));
138 inner_tys.extend(expectations_iter.by_ref().take(n_uncovered_patterns).cloned());
139 inner_tys.extend(post.iter().zip(expectations_iter).map(infer_pat));
126 140
127 Ty::apply(TypeCtor::Tuple { cardinality: args.len() as u16 }, Substs(inner_tys)) 141 Ty::apply(
142 TypeCtor::Tuple { cardinality: inner_tys.len() as u16 },
143 Substs(inner_tys.into()),
144 )
128 } 145 }
129 Pat::Or(ref pats) => { 146 Pat::Or(ref pats) => {
130 if let Some((first_pat, rest)) = pats.split_first() { 147 if let Some((first_pat, rest)) = pats.split_first() {
@@ -150,9 +167,14 @@ impl<'a> InferenceContext<'a> {
150 let subty = self.infer_pat(*pat, expectation, default_bm); 167 let subty = self.infer_pat(*pat, expectation, default_bm);
151 Ty::apply_one(TypeCtor::Ref(*mutability), subty) 168 Ty::apply_one(TypeCtor::Ref(*mutability), subty)
152 } 169 }
153 Pat::TupleStruct { path: p, args: subpats, .. } => { 170 Pat::TupleStruct { path: p, args: subpats, ellipsis } => self.infer_tuple_struct_pat(
154 self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat) 171 p.as_ref(),
155 } 172 subpats,
173 expected,
174 default_bm,
175 pat,
176 *ellipsis,
177 ),
156 Pat::Record { path: p, args: fields, ellipsis: _ } => { 178 Pat::Record { path: p, args: fields, ellipsis: _ } => {
157 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) 179 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
158 } 180 }
diff --git a/crates/hir_ty/src/tests.rs b/crates/hir_ty/src/tests.rs
index 104ef334c..0a400cb70 100644
--- a/crates/hir_ty/src/tests.rs
+++ b/crates/hir_ty/src/tests.rs
@@ -22,7 +22,8 @@ use hir_def::{
22 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, 22 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId,
23}; 23};
24use hir_expand::{db::AstDatabase, InFile}; 24use hir_expand::{db::AstDatabase, InFile};
25use stdx::{format_to, RacyFlag}; 25use once_cell::race::OnceBool;
26use stdx::format_to;
26use syntax::{ 27use syntax::{
27 algo, 28 algo,
28 ast::{self, AstNode}, 29 ast::{self, AstNode},
@@ -40,8 +41,8 @@ use crate::{
40// `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots. 41// `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
41 42
42fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> { 43fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
43 static ENABLE: RacyFlag = RacyFlag::new(); 44 static ENABLE: OnceBool = OnceBool::new();
44 if !ENABLE.get(|| env::var("CHALK_DEBUG").is_ok()) { 45 if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) {
45 return None; 46 return None;
46 } 47 }
47 48
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index 6a965ac4f..5a5f48fd0 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -679,3 +679,98 @@ fn box_pattern() {
679 "#]], 679 "#]],
680 ); 680 );
681} 681}
682
683#[test]
684fn tuple_ellipsis_pattern() {
685 check_infer(
686 r#"
687fn foo(tuple: (u8, i16, f32)) {
688 match tuple {
689 (.., b, c) => {},
690 (a, .., c) => {},
691 (a, b, ..) => {},
692 (a, b) => {/*too short*/}
693 (a, b, c, d) => {/*too long*/}
694 _ => {}
695 }
696}"#,
697 expect![[r#"
698 7..12 'tuple': (u8, i16, f32)
699 30..224 '{ ... } }': ()
700 36..222 'match ... }': ()
701 42..47 'tuple': (u8, i16, f32)
702 58..68 '(.., b, c)': (u8, i16, f32)
703 63..64 'b': i16
704 66..67 'c': f32
705 72..74 '{}': ()
706 84..94 '(a, .., c)': (u8, i16, f32)
707 85..86 'a': u8
708 92..93 'c': f32
709 98..100 '{}': ()
710 110..120 '(a, b, ..)': (u8, i16, f32)
711 111..112 'a': u8
712 114..115 'b': i16
713 124..126 '{}': ()
714 136..142 '(a, b)': (u8, i16, f32)
715 137..138 'a': u8
716 140..141 'b': i16
717 146..161 '{/*too short*/}': ()
718 170..182 '(a, b, c, d)': (u8, i16, f32, {unknown})
719 171..172 'a': u8
720 174..175 'b': i16
721 177..178 'c': f32
722 180..181 'd': {unknown}
723 186..200 '{/*too long*/}': ()
724 209..210 '_': (u8, i16, f32)
725 214..216 '{}': ()
726 "#]],
727 );
728}
729
730#[test]
731fn tuple_struct_ellipsis_pattern() {
732 check_infer(
733 r#"
734struct Tuple(u8, i16, f32);
735fn foo(tuple: Tuple) {
736 match tuple {
737 Tuple(.., b, c) => {},
738 Tuple(a, .., c) => {},
739 Tuple(a, b, ..) => {},
740 Tuple(a, b) => {/*too short*/}
741 Tuple(a, b, c, d) => {/*too long*/}
742 _ => {}
743 }
744}"#,
745 expect![[r#"
746 35..40 'tuple': Tuple
747 49..268 '{ ... } }': ()
748 55..266 'match ... }': ()
749 61..66 'tuple': Tuple
750 77..92 'Tuple(.., b, c)': Tuple
751 87..88 'b': i16
752 90..91 'c': f32
753 96..98 '{}': ()
754 108..123 'Tuple(a, .., c)': Tuple
755 114..115 'a': u8
756 121..122 'c': f32
757 127..129 '{}': ()
758 139..154 'Tuple(a, b, ..)': Tuple
759 145..146 'a': u8
760 148..149 'b': i16
761 158..160 '{}': ()
762 170..181 'Tuple(a, b)': Tuple
763 176..177 'a': u8
764 179..180 'b': i16
765 185..200 '{/*too short*/}': ()
766 209..226 'Tuple(... c, d)': Tuple
767 215..216 'a': u8
768 218..219 'b': i16
769 221..222 'c': f32
770 224..225 'd': {unknown}
771 230..244 '{/*too long*/}': ()
772 253..254 '_': Tuple
773 258..260 '{}': ()
774 "#]],
775 );
776}
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 1c7f02763..3df73ed4f 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -10,7 +10,7 @@ mod field_shorthand;
10use std::cell::RefCell; 10use std::cell::RefCell;
11 11
12use hir::{ 12use hir::{
13 diagnostics::{Diagnostic as _, DiagnosticSinkBuilder}, 13 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
14 Semantics, 14 Semantics,
15}; 15};
16use ide_db::base_db::SourceDatabase; 16use ide_db::base_db::SourceDatabase;
@@ -35,15 +35,23 @@ pub struct Diagnostic {
35 pub severity: Severity, 35 pub severity: Severity,
36 pub fix: Option<Fix>, 36 pub fix: Option<Fix>,
37 pub unused: bool, 37 pub unused: bool,
38 pub code: Option<DiagnosticCode>,
38} 39}
39 40
40impl Diagnostic { 41impl Diagnostic {
41 fn error(range: TextRange, message: String) -> Self { 42 fn error(range: TextRange, message: String) -> Self {
42 Self { message, range, severity: Severity::Error, fix: None, unused: false } 43 Self { message, range, severity: Severity::Error, fix: None, unused: false, code: None }
43 } 44 }
44 45
45 fn hint(range: TextRange, message: String) -> Self { 46 fn hint(range: TextRange, message: String) -> Self {
46 Self { message, range, severity: Severity::WeakWarning, fix: None, unused: false } 47 Self {
48 message,
49 range,
50 severity: Severity::WeakWarning,
51 fix: None,
52 unused: false,
53 code: None,
54 }
47 } 55 }
48 56
49 fn with_fix(self, fix: Option<Fix>) -> Self { 57 fn with_fix(self, fix: Option<Fix>) -> Self {
@@ -53,6 +61,10 @@ impl Diagnostic {
53 fn with_unused(self, unused: bool) -> Self { 61 fn with_unused(self, unused: bool) -> Self {
54 Self { unused, ..self } 62 Self { unused, ..self }
55 } 63 }
64
65 fn with_code(self, code: Option<DiagnosticCode>) -> Self {
66 Self { code, ..self }
67 }
56} 68}
57 69
58#[derive(Debug)] 70#[derive(Debug)]
@@ -126,7 +138,8 @@ pub(crate) fn diagnostics(
126 // Override severity and mark as unused. 138 // Override severity and mark as unused.
127 res.borrow_mut().push( 139 res.borrow_mut().push(
128 Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) 140 Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message())
129 .with_unused(true), 141 .with_unused(true)
142 .with_code(Some(d.code())),
130 ); 143 );
131 }) 144 })
132 // Only collect experimental diagnostics when they're enabled. 145 // Only collect experimental diagnostics when they're enabled.
@@ -137,8 +150,10 @@ pub(crate) fn diagnostics(
137 let mut sink = sink_builder 150 let mut sink = sink_builder
138 // Diagnostics not handled above get no fix and default treatment. 151 // Diagnostics not handled above get no fix and default treatment.
139 .build(|d| { 152 .build(|d| {
140 res.borrow_mut() 153 res.borrow_mut().push(
141 .push(Diagnostic::error(sema.diagnostics_display_range(d).range, d.message())); 154 Diagnostic::error(sema.diagnostics_display_range(d).range, d.message())
155 .with_code(Some(d.code())),
156 );
142 }); 157 });
143 158
144 if let Some(m) = sema.to_module_def(file_id) { 159 if let Some(m) = sema.to_module_def(file_id) {
@@ -149,11 +164,15 @@ pub(crate) fn diagnostics(
149} 164}
150 165
151fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 166fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
152 Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()).with_fix(d.fix(&sema)) 167 Diagnostic::error(sema.diagnostics_display_range(d).range, d.message())
168 .with_fix(d.fix(&sema))
169 .with_code(Some(d.code()))
153} 170}
154 171
155fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 172fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
156 Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()).with_fix(d.fix(&sema)) 173 Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message())
174 .with_fix(d.fix(&sema))
175 .with_code(Some(d.code()))
157} 176}
158 177
159fn check_unnecessary_braces_in_use_statement( 178fn check_unnecessary_braces_in_use_statement(
@@ -589,6 +608,11 @@ fn test_fn() {
589 }, 608 },
590 ), 609 ),
591 unused: false, 610 unused: false,
611 code: Some(
612 DiagnosticCode(
613 "unresolved-module",
614 ),
615 ),
592 }, 616 },
593 ] 617 ]
594 "#]], 618 "#]],
diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs
index 459f201ed..5cbbe306e 100644
--- a/crates/ide/src/fn_references.rs
+++ b/crates/ide/src/fn_references.rs
@@ -1,11 +1,12 @@
1//! This module implements a methods and free functions search in the specified file. 1//! This module implements a methods and free functions search in the specified file.
2//! We have to skip tests, so cannot reuse file_structure module. 2//! We have to skip tests, so cannot reuse file_structure module.
3 3
4use assists::utils::test_related_attribute;
4use hir::Semantics; 5use hir::Semantics;
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
6use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; 7use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode};
7 8
8use crate::{runnables::has_test_related_attribute, FileId, FileRange}; 9use crate::{FileId, FileRange};
9 10
10pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec<FileRange> { 11pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec<FileRange> {
11 let sema = Semantics::new(db); 12 let sema = Semantics::new(db);
@@ -15,7 +16,7 @@ pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec<FileRa
15 16
16fn method_range(item: SyntaxNode, file_id: FileId) -> Option<FileRange> { 17fn method_range(item: SyntaxNode, file_id: FileId) -> Option<FileRange> {
17 ast::Fn::cast(item).and_then(|fn_def| { 18 ast::Fn::cast(item).and_then(|fn_def| {
18 if has_test_related_attribute(&fn_def) { 19 if test_related_attribute(&fn_def).is_some() {
19 None 20 None
20 } else { 21 } else {
21 fn_def.name().map(|name| FileRange { file_id, range: name.syntax().text_range() }) 22 fn_def.name().map(|name| FileRange { file_id, range: name.syntax().text_range() })
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index e05465b32..5693dd400 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -110,14 +110,23 @@ pub(crate) fn find_all_refs(
110 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) 110 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
111 .collect(); 111 .collect();
112 112
113 let decl_range = def.try_to_nav(sema.db)?.focus_or_full_range(); 113 let nav = def.try_to_nav(sema.db)?;
114 114 let decl_range = nav.focus_or_full_range();
115 let declaration = Declaration { 115
116 nav: def.try_to_nav(sema.db)?, 116 let mut kind = ReferenceKind::Other;
117 kind: ReferenceKind::Other, 117 if let Definition::Local(local) = def {
118 access: decl_access(&def, &syntax, decl_range), 118 if let either::Either::Left(pat) = local.source(sema.db).value {
119 if matches!(
120 pat.syntax().parent().and_then(ast::RecordPatField::cast),
121 Some(pat_field) if pat_field.name_ref().is_none()
122 ) {
123 kind = ReferenceKind::FieldShorthandForLocal;
124 }
125 }
119 }; 126 };
120 127
128 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
129
121 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) 130 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
122} 131}
123 132
@@ -613,7 +622,7 @@ fn foo() {
613 expect![[r#" 622 expect![[r#"
614 f RECORD_FIELD FileId(0) 15..21 15..16 Other 623 f RECORD_FIELD FileId(0) 15..21 15..16 Other
615 624
616 FileId(0) 55..56 Other Read 625 FileId(0) 55..56 RecordFieldExprOrPat Read
617 FileId(0) 68..69 Other Write 626 FileId(0) 68..69 Other Write
618 "#]], 627 "#]],
619 ); 628 );
@@ -748,7 +757,7 @@ fn f() -> m::En {
748 expect![[r#" 757 expect![[r#"
749 field RECORD_FIELD FileId(0) 56..65 56..61 Other 758 field RECORD_FIELD FileId(0) 56..65 56..61 Other
750 759
751 FileId(0) 125..130 Other Read 760 FileId(0) 125..130 RecordFieldExprOrPat Read
752 "#]], 761 "#]],
753 ); 762 );
754 } 763 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 26ac2371a..91c64bd4a 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,17 +1,16 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::{
3 convert::TryInto,
4 error::Error,
5 fmt::{self, Display},
6};
2 7
3use hir::{Module, ModuleDef, ModuleSource, Semantics}; 8use hir::{Module, ModuleDef, ModuleSource, Semantics};
4use ide_db::base_db::SourceDatabaseExt; 9use ide_db::base_db::{FileRange, SourceDatabaseExt};
5use ide_db::{ 10use ide_db::{
6 defs::{Definition, NameClass, NameRefClass}, 11 defs::{Definition, NameClass, NameRefClass},
7 RootDatabase, 12 RootDatabase,
8}; 13};
9
10use std::{
11 convert::TryInto,
12 error::Error,
13 fmt::{self, Display},
14};
15use syntax::{ 14use syntax::{
16 algo::find_node_at_offset, 15 algo::find_node_at_offset,
17 ast::{self, NameOwner}, 16 ast::{self, NameOwner},
@@ -106,9 +105,12 @@ fn find_module_at_offset(
106 Some(module) 105 Some(module)
107} 106}
108 107
109fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { 108fn source_edit_from_reference(
109 sema: &Semantics<RootDatabase>,
110 reference: Reference,
111 new_name: &str,
112) -> SourceFileEdit {
110 let mut replacement_text = String::new(); 113 let mut replacement_text = String::new();
111 let file_id = reference.file_range.file_id;
112 let range = match reference.kind { 114 let range = match reference.kind {
113 ReferenceKind::FieldShorthandForField => { 115 ReferenceKind::FieldShorthandForField => {
114 mark::hit!(test_rename_struct_field_for_shorthand); 116 mark::hit!(test_rename_struct_field_for_shorthand);
@@ -122,12 +124,48 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
122 replacement_text.push_str(new_name); 124 replacement_text.push_str(new_name);
123 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 125 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
124 } 126 }
127 ReferenceKind::RecordFieldExprOrPat => {
128 mark::hit!(test_rename_field_expr_pat);
129 replacement_text.push_str(new_name);
130 edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name)
131 }
125 _ => { 132 _ => {
126 replacement_text.push_str(new_name); 133 replacement_text.push_str(new_name);
127 reference.file_range.range 134 reference.file_range.range
128 } 135 }
129 }; 136 };
130 SourceFileEdit { file_id, edit: TextEdit::replace(range, replacement_text) } 137 SourceFileEdit {
138 file_id: reference.file_range.file_id,
139 edit: TextEdit::replace(range, replacement_text),
140 }
141}
142
143fn edit_text_range_for_record_field_expr_or_pat(
144 sema: &Semantics<RootDatabase>,
145 file_range: FileRange,
146 new_name: &str,
147) -> TextRange {
148 let source_file = sema.parse(file_range.file_id);
149 let file_syntax = source_file.syntax();
150 let original_range = file_range.range;
151
152 syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range)
153 .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) {
154 Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()),
155 _ => None,
156 })
157 .or_else(|| {
158 syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range)
159 .and_then(|field_pat| match field_pat.pat() {
160 Some(ast::Pat::IdentPat(pat))
161 if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) =>
162 {
163 Some(field_pat.syntax().text_range())
164 }
165 _ => None,
166 })
167 })
168 .unwrap_or(original_range)
131} 169}
132 170
133fn rename_mod( 171fn rename_mod(
@@ -170,7 +208,7 @@ fn rename_mod(
170 let ref_edits = refs 208 let ref_edits = refs
171 .references 209 .references
172 .into_iter() 210 .into_iter()
173 .map(|reference| source_edit_from_reference(reference, new_name)); 211 .map(|reference| source_edit_from_reference(sema, reference, new_name));
174 source_file_edits.extend(ref_edits); 212 source_file_edits.extend(ref_edits);
175 213
176 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 214 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
@@ -211,7 +249,7 @@ fn rename_to_self(
211 249
212 let mut edits = usages 250 let mut edits = usages
213 .into_iter() 251 .into_iter()
214 .map(|reference| source_edit_from_reference(reference, "self")) 252 .map(|reference| source_edit_from_reference(sema, reference, "self"))
215 .collect::<Vec<_>>(); 253 .collect::<Vec<_>>();
216 254
217 edits.push(SourceFileEdit { 255 edits.push(SourceFileEdit {
@@ -300,7 +338,7 @@ fn rename_reference(
300 338
301 let edit = refs 339 let edit = refs
302 .into_iter() 340 .into_iter()
303 .map(|reference| source_edit_from_reference(reference, new_name)) 341 .map(|reference| source_edit_from_reference(sema, reference, new_name))
304 .collect::<Vec<_>>(); 342 .collect::<Vec<_>>();
305 343
306 if edit.is_empty() { 344 if edit.is_empty() {
@@ -1097,4 +1135,116 @@ impl Foo {
1097"#, 1135"#,
1098 ); 1136 );
1099 } 1137 }
1138
1139 #[test]
1140 fn test_initializer_use_field_init_shorthand() {
1141 mark::check!(test_rename_field_expr_pat);
1142 check(
1143 "bar",
1144 r#"
1145struct Foo { i<|>: i32 }
1146
1147fn foo(bar: i32) -> Foo {
1148 Foo { i: bar }
1149}
1150"#,
1151 r#"
1152struct Foo { bar: i32 }
1153
1154fn foo(bar: i32) -> Foo {
1155 Foo { bar }
1156}
1157"#,
1158 );
1159 }
1160
1161 #[test]
1162 fn test_struct_field_destructure_into_shorthand() {
1163 check(
1164 "baz",
1165 r#"
1166struct Foo { i<|>: i32 }
1167
1168fn foo(foo: Foo) {
1169 let Foo { i: baz } = foo;
1170 let _ = baz;
1171}
1172"#,
1173 r#"
1174struct Foo { baz: i32 }
1175
1176fn foo(foo: Foo) {
1177 let Foo { baz } = foo;
1178 let _ = baz;
1179}
1180"#,
1181 );
1182 }
1183
1184 #[test]
1185 fn test_rename_binding_in_destructure_pat() {
1186 let expected_fixture = r#"
1187struct Foo {
1188 i: i32,
1189}
1190
1191fn foo(foo: Foo) {
1192 let Foo { i: bar } = foo;
1193 let _ = bar;
1194}
1195"#;
1196 check(
1197 "bar",
1198 r#"
1199struct Foo {
1200 i: i32,
1201}
1202
1203fn foo(foo: Foo) {
1204 let Foo { i: b } = foo;
1205 let _ = b<|>;
1206}
1207"#,
1208 expected_fixture,
1209 );
1210 check(
1211 "bar",
1212 r#"
1213struct Foo {
1214 i: i32,
1215}
1216
1217fn foo(foo: Foo) {
1218 let Foo { i } = foo;
1219 let _ = i<|>;
1220}
1221"#,
1222 expected_fixture,
1223 );
1224 }
1225
1226 #[test]
1227 fn test_rename_binding_in_destructure_param_pat() {
1228 check(
1229 "bar",
1230 r#"
1231struct Foo {
1232 i: i32
1233}
1234
1235fn foo(Foo { i }: foo) -> i32 {
1236 i<|>
1237}
1238"#,
1239 r#"
1240struct Foo {
1241 i: i32
1242}
1243
1244fn foo(Foo { i: bar }: foo) -> i32 {
1245 bar
1246}
1247"#,
1248 )
1249 }
1100} 1250}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 2bd0e86e5..e15411777 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -1,5 +1,6 @@
1use std::fmt; 1use std::fmt;
2 2
3use assists::utils::test_related_attribute;
3use cfg::CfgExpr; 4use cfg::CfgExpr;
4use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; 5use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
@@ -156,7 +157,7 @@ fn runnable_fn(
156 None => TestId::Name(name_string), 157 None => TestId::Name(name_string),
157 }; 158 };
158 159
159 if has_test_related_attribute(&fn_def) { 160 if test_related_attribute(&fn_def).is_some() {
160 let attr = TestAttr::from_fn(&fn_def); 161 let attr = TestAttr::from_fn(&fn_def);
161 RunnableKind::Test { test_id, attr } 162 RunnableKind::Test { test_id, attr }
162 } else if fn_def.has_atom_attr("bench") { 163 } else if fn_def.has_atom_attr("bench") {
@@ -235,20 +236,6 @@ impl TestAttr {
235 } 236 }
236} 237}
237 238
238/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
239/// `#[test_case(...)]`, `#[tokio::test]` and similar.
240/// Also a regular `#[test]` annotation is supported.
241///
242/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
243/// but it's better than not to have the runnables for the tests at all.
244pub(crate) fn has_test_related_attribute(fn_def: &ast::Fn) -> bool {
245 fn_def
246 .attrs()
247 .filter_map(|attr| attr.path())
248 .map(|path| path.syntax().to_string().to_lowercase())
249 .any(|attribute_text| attribute_text.contains("test"))
250}
251
252const RUSTDOC_FENCE: &str = "```"; 239const RUSTDOC_FENCE: &str = "```";
253const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] = 240const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
254 &["", "rust", "should_panic", "edition2015", "edition2018"]; 241 &["", "rust", "should_panic", "edition2015", "edition2018"];
@@ -307,7 +294,7 @@ fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool {
307 for item in item_list.items() { 294 for item in item_list.items() {
308 match item { 295 match item {
309 ast::Item::Fn(f) => { 296 ast::Item::Fn(f) => {
310 if has_test_related_attribute(&f) { 297 if test_related_attribute(&f).is_some() {
311 return true; 298 return true;
312 } 299 }
313 } 300 }
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
index 8e91c99d7..b75f88ed9 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -1,6 +1,6 @@
1use std::{fmt, iter::FromIterator, sync::Arc}; 1use std::{fmt, iter::FromIterator, sync::Arc};
2 2
3use hir::MacroFile; 3use hir::{MacroFile, MacroResult};
4use ide_db::base_db::{ 4use ide_db::base_db::{
5 salsa::debug::{DebugQueryTable, TableEntry}, 5 salsa::debug::{DebugQueryTable, TableEntry},
6 CrateId, FileId, FileTextQuery, SourceDatabase, SourceRootId, 6 CrateId, FileId, FileTextQuery, SourceDatabase, SourceRootId,
@@ -19,7 +19,7 @@ fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
19 ide_db::base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>() 19 ide_db::base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>()
20} 20}
21fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 21fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
22 hir::db::ParseMacroQuery.in_db(db).entries::<SyntaxTreeStats>() 22 hir::db::ParseMacroExpansionQuery.in_db(db).entries::<SyntaxTreeStats>()
23} 23}
24 24
25// Feature: Status 25// Feature: Status
@@ -115,10 +115,12 @@ impl FromIterator<TableEntry<FileId, Parse<ast::SourceFile>>> for SyntaxTreeStat
115 } 115 }
116} 116}
117 117
118impl<M> FromIterator<TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>> for SyntaxTreeStats { 118impl<M> FromIterator<TableEntry<MacroFile, MacroResult<(Parse<SyntaxNode>, M)>>>
119 for SyntaxTreeStats
120{
119 fn from_iter<T>(iter: T) -> SyntaxTreeStats 121 fn from_iter<T>(iter: T) -> SyntaxTreeStats
120 where 122 where
121 T: IntoIterator<Item = TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>>, 123 T: IntoIterator<Item = TableEntry<MacroFile, MacroResult<(Parse<SyntaxNode>, M)>>>,
122 { 124 {
123 let mut res = SyntaxTreeStats::default(); 125 let mut res = SyntaxTreeStats::default();
124 for entry in iter { 126 for entry in iter {
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 05bafe9c8..1ed77b40b 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -6,7 +6,7 @@ pub(crate) mod tags;
6#[cfg(test)] 6#[cfg(test)]
7mod tests; 7mod tests;
8 8
9use hir::{Local, Name, Semantics, VariantDef}; 9use hir::{AsAssocItem, Local, Name, Semantics, VariantDef};
10use ide_db::{ 10use ide_db::{
11 defs::{Definition, NameClass, NameRefClass}, 11 defs::{Definition, NameClass, NameRefClass},
12 RootDatabase, 12 RootDatabase,
@@ -557,7 +557,9 @@ fn highlight_element(
557 h 557 h
558 } 558 }
559 } 559 }
560 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] => HighlightTag::Operator.into(), 560 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => {
561 HighlightTag::Operator.into()
562 }
561 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { 563 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
562 HighlightTag::Macro.into() 564 HighlightTag::Macro.into()
563 } 565 }
@@ -744,6 +746,9 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
744 if func.is_unsafe(db) { 746 if func.is_unsafe(db) {
745 h |= HighlightModifier::Unsafe; 747 h |= HighlightModifier::Unsafe;
746 } 748 }
749 if func.as_assoc_item(db).is_some() && func.self_param(db).is_none() {
750 h |= HighlightModifier::Static;
751 }
747 return h; 752 return h;
748 } 753 }
749 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, 754 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index e8f78ad52..65e0671a5 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -65,6 +65,8 @@ pub enum HighlightModifier {
65 Consuming, 65 Consuming,
66 Unsafe, 66 Unsafe,
67 Callable, 67 Callable,
68 /// Used for associated functions
69 Static,
68} 70}
69 71
70impl HighlightTag { 72impl HighlightTag {
@@ -124,6 +126,7 @@ impl HighlightModifier {
124 HighlightModifier::Consuming, 126 HighlightModifier::Consuming,
125 HighlightModifier::Unsafe, 127 HighlightModifier::Unsafe,
126 HighlightModifier::Callable, 128 HighlightModifier::Callable,
129 HighlightModifier::Static,
127 ]; 130 ];
128 131
129 fn as_str(self) -> &'static str { 132 fn as_str(self) -> &'static str {
@@ -137,6 +140,7 @@ impl HighlightModifier {
137 HighlightModifier::Consuming => "consuming", 140 HighlightModifier::Consuming => "consuming",
138 HighlightModifier::Unsafe => "unsafe", 141 HighlightModifier::Unsafe => "unsafe",
139 HighlightModifier::Callable => "callable", 142 HighlightModifier::Callable => "callable",
143 HighlightModifier::Static => "static",
140 } 144 }
141 } 145 }
142 146
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
new file mode 100644
index 000000000..cd80d72b7
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -0,0 +1,56 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="keyword">fn</span> <span class="function declaration">not_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
39
40<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="punctuation">{</span><span class="punctuation">}</span>
41
42<span class="keyword">impl</span> <span class="struct">foo</span> <span class="punctuation">{</span>
43 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static">is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
44 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
45<span class="punctuation">}</span>
46
47<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="punctuation">{</span>
48 <span class="keyword">fn</span> <span class="function declaration static">t_is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
49 <span class="keyword">fn</span> <span class="function declaration">t_is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
50<span class="punctuation">}</span>
51
52<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="punctuation">{</span>
53 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static">is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
54 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
55<span class="punctuation">}</span>
56 </code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 6322d404f..6be88f856 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -53,7 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
53 <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute injected">#</span><span class="attribute injected">!</span><span class="attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation injected">(</span><span class="attribute injected">unused_mut</span><span class="punctuation injected">)</span><span class="attribute injected">]</span> 53 <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute injected">#</span><span class="attribute injected">!</span><span class="attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation injected">(</span><span class="attribute injected">unused_mut</span><span class="punctuation injected">)</span><span class="attribute injected">]</span>
54 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="keyword injected">mut</span><span class="generic injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected"> 54 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="keyword injected">mut</span><span class="generic injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
55</span> <span class="comment documentation">/// ```</span> 55</span> <span class="comment documentation">/// ```</span>
56 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 56 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
57 <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span> 57 <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span>
58 <span class="punctuation">}</span> 58 <span class="punctuation">}</span>
59 59
@@ -67,9 +67,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
67 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 67 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
68 <span class="comment documentation">///</span> 68 <span class="comment documentation">///</span>
69 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span> 69 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span>
70 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="punctuation injected">(</span><span class="generic injected">foo</span><span class="punctuation injected">.</span><span class="generic injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 70 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="punctuation injected">(</span><span class="generic injected">foo</span><span class="operator injected">.</span><span class="generic injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
71 <span class="comment documentation">///</span> 71 <span class="comment documentation">///</span>
72 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">bar</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="variable injected">foo</span><span class="punctuation injected">.</span><span class="field injected">bar</span><span class="generic injected"> </span><span class="operator injected">||</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span> 72 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">bar</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="generic injected"> </span><span class="operator injected">||</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span>
73 <span class="comment documentation">///</span> 73 <span class="comment documentation">///</span>
74 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line 74 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line
75 </span><span class="comment documentation">/// </span><span class="comment injected"> comment */</span> 75 </span><span class="comment documentation">/// </span><span class="comment injected"> comment */</span>
@@ -81,7 +81,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
81 <span class="comment documentation">/// ```</span> 81 <span class="comment documentation">/// ```</span>
82 <span class="comment documentation">///</span> 82 <span class="comment documentation">///</span>
83 <span class="comment documentation">/// ```rust,no_run</span> 83 <span class="comment documentation">/// ```rust,no_run</span>
84 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">foobar</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected"> 84 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">foobar</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
85</span> <span class="comment documentation">/// ```</span> 85</span> <span class="comment documentation">/// ```</span>
86 <span class="comment documentation">///</span> 86 <span class="comment documentation">///</span>
87 <span class="comment documentation">/// ```sh</span> 87 <span class="comment documentation">/// ```sh</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 18addd00d..57c178916 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -40,7 +40,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
40<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 40<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
41 <span class="function">fixture</span><span class="punctuation">(</span><span class="string_literal">r#"</span> 41 <span class="function">fixture</span><span class="punctuation">(</span><span class="string_literal">r#"</span>
42 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="punctuation">{</span> 42 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="punctuation">{</span>
43 <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 43 <span class="keyword">fn</span> <span class="function declaration static">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
44 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="punctuation">,</span> <span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">;</span> 44 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="punctuation">,</span> <span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">;</span>
45 <span class="punctuation">}</span> 45 <span class="punctuation">}</span>
46 <span class="punctuation">}</span><span class="string_literal">"#</span> 46 <span class="punctuation">}</span><span class="string_literal">"#</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 43f1b32fd..d398e1ec8 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -93,4 +93,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
93 93
94 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> A <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span> 94 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> A <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
95 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> ничоси <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span> 95 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> ничоси <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
96
97 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">x</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> "</span><span class="punctuation">,</span> thingy<span class="punctuation">,</span> n2<span class="punctuation">)</span><span class="punctuation">;</span>
96<span class="punctuation">}</span></code></pre> \ No newline at end of file 98<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 552fea668..4b6d6adc9 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -73,27 +73,27 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
73 <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span> 73 <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span>
74 <span class="comment">// unsafe fn and method calls</span> 74 <span class="comment">// unsafe fn and method calls</span>
75 <span class="function unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 75 <span class="function unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
76 <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="punctuation">.</span><span class="field unsafe">b</span><span class="punctuation">;</span> 76 <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="operator">.</span><span class="field unsafe">b</span><span class="punctuation">;</span>
77 <span class="keyword control">match</span> <span class="variable">u</span> <span class="punctuation">{</span> 77 <span class="keyword control">match</span> <span class="variable">u</span> <span class="punctuation">{</span>
78 <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> 78 <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
79 <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">a</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> 79 <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">a</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
80 <span class="punctuation">}</span> 80 <span class="punctuation">}</span>
81 <span class="struct">HasUnsafeFn</span><span class="punctuation">.</span><span class="function unsafe">unsafe_method</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 81 <span class="struct">HasUnsafeFn</span><span class="operator">.</span><span class="function unsafe">unsafe_method</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
82 82
83 <span class="comment">// unsafe deref</span> 83 <span class="comment">// unsafe deref</span>
84 <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="variable">x</span><span class="punctuation">;</span> 84 <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="variable">x</span><span class="punctuation">;</span>
85 85
86 <span class="comment">// unsafe access to a static mut</span> 86 <span class="comment">// unsafe access to a static mut</span>
87 <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="static mutable unsafe">global_mut</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span> 87 <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="static mutable unsafe">global_mut</span><span class="operator">.</span><span class="field">a</span><span class="punctuation">;</span>
88 88
89 <span class="comment">// unsafe ref of packed fields</span> 89 <span class="comment">// unsafe ref of packed fields</span>
90 <span class="keyword">let</span> <span class="variable declaration">packed</span> <span class="operator">=</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span> 90 <span class="keyword">let</span> <span class="variable declaration">packed</span> <span class="operator">=</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
91 <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="operator unsafe">&</span><span class="variable">packed</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span> 91 <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="operator unsafe">&</span><span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="punctuation">;</span>
92 <span class="keyword">let</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span> 92 <span class="keyword">let</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="punctuation">;</span>
93 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="keyword unsafe">ref</span> <span class="field">a</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">;</span> 93 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="keyword unsafe">ref</span> <span class="field">a</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">;</span>
94 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">;</span> 94 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">;</span>
95 95
96 <span class="comment">// unsafe auto ref of packed field</span> 96 <span class="comment">// unsafe auto ref of packed field</span>
97 <span class="variable">packed</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">.</span><span class="function unsafe">calls_autoref</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 97 <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function unsafe">calls_autoref</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
98 <span class="punctuation">}</span> 98 <span class="punctuation">}</span>
99<span class="punctuation">}</span></code></pre> \ No newline at end of file 99<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 5eb222ee2..6a10a9dcd 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -67,21 +67,21 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
67 67
68<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 68<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
69 <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 69 <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
70 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> 70 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
71 <span class="punctuation">}</span> 71 <span class="punctuation">}</span>
72<span class="punctuation">}</span> 72<span class="punctuation">}</span>
73 73
74<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 74<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
75 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 75 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
76 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword mutable consuming">self</span><span class="punctuation">)</span> 76 <span class="value_param">f</span><span class="operator">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword mutable consuming">self</span><span class="punctuation">)</span>
77 <span class="punctuation">}</span> 77 <span class="punctuation">}</span>
78 78
79 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 79 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
80 <span class="self_keyword mutable">self</span><span class="punctuation">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span> 80 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span>
81 <span class="punctuation">}</span> 81 <span class="punctuation">}</span>
82 82
83 <span class="keyword">fn</span> <span class="function declaration">quop</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 83 <span class="keyword">fn</span> <span class="function declaration">quop</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
84 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> 84 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
85 <span class="punctuation">}</span> 85 <span class="punctuation">}</span>
86<span class="punctuation">}</span> 86<span class="punctuation">}</span>
87 87
@@ -92,15 +92,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
92 92
93<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> 93<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span>
94 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">FooCopy</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span> 94 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">FooCopy</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
95 <span class="value_param">f</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span> 95 <span class="value_param">f</span><span class="operator">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span>
96 <span class="punctuation">}</span> 96 <span class="punctuation">}</span>
97 97
98 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 98 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
99 <span class="self_keyword mutable">self</span><span class="punctuation">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span> 99 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span>
100 <span class="punctuation">}</span> 100 <span class="punctuation">}</span>
101 101
102 <span class="keyword">fn</span> <span class="function declaration">quop</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span> 102 <span class="keyword">fn</span> <span class="function declaration">quop</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
103 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> 103 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
104 <span class="punctuation">}</span> 104 <span class="punctuation">}</span>
105<span class="punctuation">}</span> 105<span class="punctuation">}</span>
106 106
@@ -152,10 +152,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
152 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> <span class="operator">=</span> <span class="unresolved_reference">Vec</span><span class="operator">::</span><span class="unresolved_reference">new</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 152 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> <span class="operator">=</span> <span class="unresolved_reference">Vec</span><span class="operator">::</span><span class="unresolved_reference">new</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
153 <span class="keyword control">if</span> <span class="bool_literal">true</span> <span class="punctuation">{</span> 153 <span class="keyword control">if</span> <span class="bool_literal">true</span> <span class="punctuation">{</span>
154 <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">;</span> 154 <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">;</span>
155 <span class="variable mutable">vec</span><span class="punctuation">.</span><span class="unresolved_reference">push</span><span class="punctuation">(</span><span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="numeric_literal">1</span> <span class="punctuation">}</span><span class="punctuation">)</span><span class="punctuation">;</span> 155 <span class="variable mutable">vec</span><span class="operator">.</span><span class="unresolved_reference">push</span><span class="punctuation">(</span><span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="numeric_literal">1</span> <span class="punctuation">}</span><span class="punctuation">)</span><span class="punctuation">;</span>
156 <span class="punctuation">}</span> 156 <span class="punctuation">}</span>
157 <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span> 157 <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span>
158 <span class="variable mutable">vec</span><span class="punctuation">.</span><span class="unresolved_reference">set_len</span><span class="punctuation">(</span><span class="numeric_literal">0</span><span class="punctuation">)</span><span class="punctuation">;</span> 158 <span class="variable mutable">vec</span><span class="operator">.</span><span class="unresolved_reference">set_len</span><span class="punctuation">(</span><span class="numeric_literal">0</span><span class="punctuation">)</span><span class="punctuation">;</span>
159 <span class="static mutable unsafe">STATIC_MUT</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="punctuation">;</span> 159 <span class="static mutable unsafe">STATIC_MUT</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="punctuation">;</span>
160 <span class="punctuation">}</span> 160 <span class="punctuation">}</span>
161 161
@@ -175,14 +175,14 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
175 175
176 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> 176 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
177 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> 177 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
178 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 178 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
179 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 179 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
180 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="variable consuming">foo2</span><span class="punctuation">)</span><span class="punctuation">;</span> 180 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="variable consuming">foo2</span><span class="punctuation">)</span><span class="punctuation">;</span>
181 181
182 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> 182 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
183 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 183 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
184 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 184 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
185 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span> 185 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span>
186 186
187 <span class="keyword">let</span> <span class="variable declaration callable">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="punctuation">;</span> 187 <span class="keyword">let</span> <span class="variable declaration callable">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="punctuation">;</span>
188 <span class="keyword">let</span> <span class="variable declaration callable">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function">baz</span><span class="punctuation">;</span> 188 <span class="keyword">let</span> <span class="variable declaration callable">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function">baz</span><span class="punctuation">;</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html b/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html
index 401e87a73..c7589605f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html
@@ -37,11 +37,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
37</style> 37</style>
38<pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 38<pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
39 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span> 39 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span>
40 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(76,47%,83%);">x</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 40 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(76,47%,83%);">x</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
41 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(15,86%,51%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 41 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(15,86%,51%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
42 42
43 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="punctuation">;</span> 43 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="punctuation">;</span>
44 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(90,74%,79%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 44 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(90,74%,79%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
45<span class="punctuation">}</span> 45<span class="punctuation">}</span>
46 46
47<span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 47<span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 2b667b0d4..1dc018a16 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -340,6 +340,8 @@ fn main() {
340 340
341 println!("{\x41}", A = 92); 341 println!("{\x41}", A = 92);
342 println!("{ничоси}", ничоси = 92); 342 println!("{ничоси}", ничоси = 92);
343
344 println!("{:x?} {} ", thingy, n2);
343}"# 345}"#
344 .trim(), 346 .trim(),
345 expect_file!["./test_data/highlight_strings.html"], 347 expect_file!["./test_data/highlight_strings.html"],
@@ -513,6 +515,34 @@ fn test_extern_crate() {
513 ); 515 );
514} 516}
515 517
518#[test]
519fn test_associated_function() {
520 check_highlighting(
521 r#"
522fn not_static() {}
523
524struct foo {}
525
526impl foo {
527 pub fn is_static() {}
528 pub fn is_not_static(&self) {}
529}
530
531trait t {
532 fn t_is_static() {}
533 fn t_is_not_static(&self) {}
534}
535
536impl t for foo {
537 pub fn is_static() {}
538 pub fn is_not_static(&self) {}
539}
540 "#,
541 expect_file!["./test_data/highlight_assoc_functions.html"],
542 false,
543 )
544}
545
516/// Highlights the code given by the `ra_fixture` argument, renders the 546/// Highlights the code given by the `ra_fixture` argument, renders the
517/// result as HTML, and compares it with the HTML file given as `snapshot`. 547/// result as HTML, and compares it with the HTML file given as `snapshot`.
518/// Note that the `snapshot` file is overwritten by the rendered HTML. 548/// Note that the `snapshot` file is overwritten by the rendered HTML.
diff --git a/crates/ide_db/src/apply_change.rs b/crates/ide_db/src/apply_change.rs
index da16fa21d..987191fe3 100644
--- a/crates/ide_db/src/apply_change.rs
+++ b/crates/ide_db/src/apply_change.rs
@@ -76,7 +76,7 @@ impl RootDatabase {
76 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); 76 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
77 77
78 base_db::ParseQuery.in_db(self).sweep(sweep); 78 base_db::ParseQuery.in_db(self).sweep(sweep);
79 hir::db::ParseMacroQuery.in_db(self).sweep(sweep); 79 hir::db::ParseMacroExpansionQuery.in_db(self).sweep(sweep);
80 80
81 // Macros do take significant space, but less then the syntax trees 81 // Macros do take significant space, but less then the syntax trees
82 // self.query(hir::db::MacroDefQuery).sweep(sweep); 82 // self.query(hir::db::MacroDefQuery).sweep(sweep);
@@ -143,7 +143,7 @@ impl RootDatabase {
143 hir::db::AstIdMapQuery 143 hir::db::AstIdMapQuery
144 hir::db::MacroArgTextQuery 144 hir::db::MacroArgTextQuery
145 hir::db::MacroDefQuery 145 hir::db::MacroDefQuery
146 hir::db::ParseMacroQuery 146 hir::db::ParseMacroExpansionQuery
147 hir::db::MacroExpandQuery 147 hir::db::MacroExpandQuery
148 148
149 // DefDatabase 149 // DefDatabase
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index df74be00b..9d8ea7368 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -1,36 +1,70 @@
1//! This module contains an import search funcionality that is provided to the assists module. 1//! This module contains an import search funcionality that is provided to the assists module.
2//! Later, this should be moved away to a separate crate that is accessible from the assists module. 2//! Later, this should be moved away to a separate crate that is accessible from the assists module.
3 3
4use hir::{Crate, MacroDef, ModuleDef, Semantics}; 4use hir::{import_map, Crate, MacroDef, ModuleDef, Semantics};
5use syntax::{ast, AstNode, SyntaxKind::NAME}; 5use syntax::{ast, AstNode, SyntaxKind::NAME};
6 6
7use crate::{ 7use crate::{
8 defs::{Definition, NameClass}, 8 defs::{Definition, NameClass},
9 symbol_index::{self, FileSymbol, Query}, 9 symbol_index::{self, FileSymbol},
10 RootDatabase, 10 RootDatabase,
11}; 11};
12use either::Either; 12use either::Either;
13use rustc_hash::FxHashSet; 13use rustc_hash::FxHashSet;
14 14
15pub fn find_imports<'a>( 15pub fn find_exact_imports<'a>(
16 sema: &Semantics<'a, RootDatabase>, 16 sema: &Semantics<'a, RootDatabase>,
17 krate: Crate, 17 krate: Crate,
18 name_to_import: &str, 18 name_to_import: &str,
19) -> Vec<Either<ModuleDef, MacroDef>> { 19) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
20 let _p = profile::span("search_for_imports"); 20 let _p = profile::span("find_exact_imports");
21 find_imports(
22 sema,
23 krate,
24 {
25 let mut local_query = symbol_index::Query::new(name_to_import.to_string());
26 local_query.exact();
27 local_query.limit(40);
28 local_query
29 },
30 import_map::Query::new(name_to_import).anchor_end().case_sensitive().limit(40),
31 )
32}
33
34pub fn find_similar_imports<'a>(
35 sema: &Semantics<'a, RootDatabase>,
36 krate: Crate,
37 name_to_import: &str,
38 limit: usize,
39) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
40 let _p = profile::span("find_similar_imports");
41 find_imports(
42 sema,
43 krate,
44 {
45 let mut local_query = symbol_index::Query::new(name_to_import.to_string());
46 local_query.limit(limit);
47 local_query
48 },
49 import_map::Query::new(name_to_import).limit(limit),
50 )
51}
52
53fn find_imports<'a>(
54 sema: &Semantics<'a, RootDatabase>,
55 krate: Crate,
56 local_query: symbol_index::Query,
57 external_query: import_map::Query,
58) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
59 let _p = profile::span("find_similar_imports");
21 let db = sema.db; 60 let db = sema.db;
22 61
23 // Query dependencies first. 62 // Query dependencies first.
24 let mut candidates: FxHashSet<_> = 63 let mut candidates: FxHashSet<_> =
25 krate.query_external_importables(db, name_to_import).collect(); 64 krate.query_external_importables(db, external_query).collect();
26 65
27 // Query the local crate using the symbol index. 66 // Query the local crate using the symbol index.
28 let local_results = { 67 let local_results = symbol_index::crate_symbols(db, krate.into(), local_query);
29 let mut query = Query::new(name_to_import.to_string());
30 query.exact();
31 query.limit(40);
32 symbol_index::crate_symbols(db, krate.into(), query)
33 };
34 68
35 candidates.extend( 69 candidates.extend(
36 local_results 70 local_results
@@ -43,7 +77,7 @@ pub fn find_imports<'a>(
43 }), 77 }),
44 ); 78 );
45 79
46 candidates.into_iter().collect() 80 candidates.into_iter()
47} 81}
48 82
49fn get_name_definition<'a>( 83fn get_name_definition<'a>(
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 38ebdbf79..05139a651 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -113,7 +113,7 @@ impl RootDatabase {
113 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) { 113 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
114 let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_LRU_CAP); 114 let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_LRU_CAP);
115 base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity); 115 base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
116 hir::db::ParseMacroQuery.in_db_mut(self).set_lru_capacity(lru_capacity); 116 hir::db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
117 hir::db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(lru_capacity); 117 hir::db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
118 } 118 }
119} 119}
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index a24335240..a3e765d05 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -30,6 +30,7 @@ pub enum ReferenceKind {
30 FieldShorthandForField, 30 FieldShorthandForField,
31 FieldShorthandForLocal, 31 FieldShorthandForLocal,
32 StructLiteral, 32 StructLiteral,
33 RecordFieldExprOrPat,
33 Other, 34 Other,
34} 35}
35 36
@@ -278,8 +279,9 @@ impl<'a> FindUsages<'a> {
278 ) -> bool { 279 ) -> bool {
279 match NameRefClass::classify(self.sema, &name_ref) { 280 match NameRefClass::classify(self.sema, &name_ref) {
280 Some(NameRefClass::Definition(def)) if &def == self.def => { 281 Some(NameRefClass::Definition(def)) if &def == self.def => {
281 let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) 282 let kind = if is_record_field_expr_or_pat(&name_ref) {
282 { 283 ReferenceKind::RecordFieldExprOrPat
284 } else if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) {
283 ReferenceKind::StructLiteral 285 ReferenceKind::StructLiteral
284 } else { 286 } else {
285 ReferenceKind::Other 287 ReferenceKind::Other
@@ -385,3 +387,17 @@ fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
385 .map(|p| p.name_ref().as_ref() == Some(name_ref)) 387 .map(|p| p.name_ref().as_ref() == Some(name_ref))
386 .unwrap_or(false) 388 .unwrap_or(false)
387} 389}
390
391fn is_record_field_expr_or_pat(name_ref: &ast::NameRef) -> bool {
392 if let Some(parent) = name_ref.syntax().parent() {
393 match_ast! {
394 match parent {
395 ast::RecordExprField(it) => true,
396 ast::RecordPatField(_it) => true,
397 _ => false,
398 }
399 }
400 } else {
401 false
402 }
403}
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 780bc470a..ad29b82f7 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -112,7 +112,7 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker) -> Result<(), Marker> {
112 has_mods = true; 112 has_mods = true;
113 } 113 }
114 114
115 if p.at(T![extern]) { 115 if p.at(T![extern]) && p.nth(1) != T!['{'] && (p.nth(1) != STRING || p.nth(2) != T!['{']) {
116 has_mods = true; 116 has_mods = true;
117 abi(p); 117 abi(p);
118 } 118 }
@@ -181,6 +181,14 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker) -> Result<(), Marker> {
181 T![type] => { 181 T![type] => {
182 type_alias(p, m); 182 type_alias(p, m);
183 } 183 }
184
185 // unsafe extern "C" {}
186 T![extern] => {
187 abi(p);
188 extern_item_list(p);
189 m.complete(p, EXTERN_BLOCK);
190 }
191
184 _ => { 192 _ => {
185 if !has_visibility && !has_mods { 193 if !has_visibility && !has_mods {
186 return Err(m); 194 return Err(m);
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index d5f6a4025..540b57ae4 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -64,6 +64,13 @@ pub struct CargoConfig {
64 64
65 /// rustc target 65 /// rustc target
66 pub target: Option<String>, 66 pub target: Option<String>,
67
68 /// Don't load sysroot crates (`std`, `core` & friends). Might be useful
69 /// when debugging isolated issues.
70 pub no_sysroot: bool,
71
72 /// rustc private crate source
73 pub rustc_source: Option<AbsPathBuf>,
67} 74}
68 75
69pub type Package = Idx<PackageData>; 76pub type Package = Idx<PackageData>;
@@ -137,27 +144,27 @@ impl PackageData {
137impl CargoWorkspace { 144impl CargoWorkspace {
138 pub fn from_cargo_metadata( 145 pub fn from_cargo_metadata(
139 cargo_toml: &AbsPath, 146 cargo_toml: &AbsPath,
140 cargo_features: &CargoConfig, 147 config: &CargoConfig,
141 ) -> Result<CargoWorkspace> { 148 ) -> Result<CargoWorkspace> {
142 let mut meta = MetadataCommand::new(); 149 let mut meta = MetadataCommand::new();
143 meta.cargo_path(toolchain::cargo()); 150 meta.cargo_path(toolchain::cargo());
144 meta.manifest_path(cargo_toml.to_path_buf()); 151 meta.manifest_path(cargo_toml.to_path_buf());
145 if cargo_features.all_features { 152 if config.all_features {
146 meta.features(CargoOpt::AllFeatures); 153 meta.features(CargoOpt::AllFeatures);
147 } else { 154 } else {
148 if cargo_features.no_default_features { 155 if config.no_default_features {
149 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 156 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
150 // https://github.com/oli-obk/cargo_metadata/issues/79 157 // https://github.com/oli-obk/cargo_metadata/issues/79
151 meta.features(CargoOpt::NoDefaultFeatures); 158 meta.features(CargoOpt::NoDefaultFeatures);
152 } 159 }
153 if !cargo_features.features.is_empty() { 160 if !config.features.is_empty() {
154 meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); 161 meta.features(CargoOpt::SomeFeatures(config.features.clone()));
155 } 162 }
156 } 163 }
157 if let Some(parent) = cargo_toml.parent() { 164 if let Some(parent) = cargo_toml.parent() {
158 meta.current_dir(parent.to_path_buf()); 165 meta.current_dir(parent.to_path_buf());
159 } 166 }
160 if let Some(target) = cargo_features.target.as_ref() { 167 if let Some(target) = config.target.as_ref() {
161 meta.other_options(vec![String::from("--filter-platform"), target.clone()]); 168 meta.other_options(vec![String::from("--filter-platform"), target.clone()]);
162 } 169 }
163 let mut meta = meta.exec().with_context(|| { 170 let mut meta = meta.exec().with_context(|| {
@@ -167,8 +174,8 @@ impl CargoWorkspace {
167 let mut out_dir_by_id = FxHashMap::default(); 174 let mut out_dir_by_id = FxHashMap::default();
168 let mut cfgs = FxHashMap::default(); 175 let mut cfgs = FxHashMap::default();
169 let mut proc_macro_dylib_paths = FxHashMap::default(); 176 let mut proc_macro_dylib_paths = FxHashMap::default();
170 if cargo_features.load_out_dirs_from_check { 177 if config.load_out_dirs_from_check {
171 let resources = load_extern_resources(cargo_toml, cargo_features)?; 178 let resources = load_extern_resources(cargo_toml, config)?;
172 out_dir_by_id = resources.out_dirs; 179 out_dir_by_id = resources.out_dirs;
173 cfgs = resources.cfgs; 180 cfgs = resources.cfgs;
174 proc_macro_dylib_paths = resources.proc_dylib_paths; 181 proc_macro_dylib_paths = resources.proc_dylib_paths;
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index e92cfea59..24aa9b8fa 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -4,69 +4,27 @@ mod cargo_workspace;
4mod project_json; 4mod project_json;
5mod sysroot; 5mod sysroot;
6mod cfg_flag; 6mod cfg_flag;
7mod workspace;
7 8
8use std::{ 9use std::{
9 fmt, 10 fs::{read_dir, ReadDir},
10 fs::{self, read_dir, ReadDir},
11 io, 11 io,
12 process::Command, 12 process::Command,
13}; 13};
14 14
15use anyhow::{bail, Context, Result}; 15use anyhow::{bail, Context, Result};
16use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
17use cfg::CfgOptions;
18use paths::{AbsPath, AbsPathBuf}; 16use paths::{AbsPath, AbsPathBuf};
19use rustc_hash::{FxHashMap, FxHashSet}; 17use rustc_hash::FxHashSet;
20
21use crate::cfg_flag::CfgFlag;
22 18
23pub use crate::{ 19pub use crate::{
24 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, 20 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
25 project_json::{ProjectJson, ProjectJsonData}, 21 project_json::{ProjectJson, ProjectJsonData},
26 sysroot::Sysroot, 22 sysroot::Sysroot,
23 workspace::{PackageRoot, ProjectWorkspace},
27}; 24};
28 25
29pub use proc_macro_api::ProcMacroClient; 26pub use proc_macro_api::ProcMacroClient;
30 27
31#[derive(Clone, Eq, PartialEq)]
32pub enum ProjectWorkspace {
33 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
34 Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
35 /// Project workspace was manually specified using a `rust-project.json` file.
36 Json { project: ProjectJson, sysroot: Option<Sysroot> },
37}
38
39impl fmt::Debug for ProjectWorkspace {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 ProjectWorkspace::Cargo { cargo, sysroot } => f
43 .debug_struct("Cargo")
44 .field("n_packages", &cargo.packages().len())
45 .field("n_sysroot_crates", &sysroot.crates().len())
46 .finish(),
47 ProjectWorkspace::Json { project, sysroot } => {
48 let mut debug_struct = f.debug_struct("Json");
49 debug_struct.field("n_crates", &project.n_crates());
50 if let Some(sysroot) = sysroot {
51 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
52 }
53 debug_struct.finish()
54 }
55 }
56 }
57}
58
59/// `PackageRoot` describes a package root folder.
60/// Which may be an external dependency, or a member of
61/// the current workspace.
62#[derive(Debug, Clone, Eq, PartialEq, Hash)]
63pub struct PackageRoot {
64 /// Is a member of the current workspace
65 pub is_member: bool,
66 pub include: Vec<AbsPathBuf>,
67 pub exclude: Vec<AbsPathBuf>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] 28#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
71pub enum ProjectManifest { 29pub enum ProjectManifest {
72 ProjectJson(AbsPathBuf), 30 ProjectJson(AbsPathBuf),
@@ -153,376 +111,6 @@ impl ProjectManifest {
153 } 111 }
154} 112}
155 113
156impl ProjectWorkspace {
157 pub fn load(
158 manifest: ProjectManifest,
159 cargo_config: &CargoConfig,
160 with_sysroot: bool,
161 ) -> Result<ProjectWorkspace> {
162 let res = match manifest {
163 ProjectManifest::ProjectJson(project_json) => {
164 let file = fs::read_to_string(&project_json).with_context(|| {
165 format!("Failed to read json file {}", project_json.display())
166 })?;
167 let data = serde_json::from_str(&file).with_context(|| {
168 format!("Failed to deserialize json file {}", project_json.display())
169 })?;
170 let project_location = project_json.parent().unwrap().to_path_buf();
171 let project = ProjectJson::new(&project_location, data);
172 let sysroot = match &project.sysroot_src {
173 Some(path) => Some(Sysroot::load(path)?),
174 None => None,
175 };
176 ProjectWorkspace::Json { project, sysroot }
177 }
178 ProjectManifest::CargoToml(cargo_toml) => {
179 let cargo_version = utf8_stdout({
180 let mut cmd = Command::new(toolchain::cargo());
181 cmd.arg("--version");
182 cmd
183 })?;
184
185 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config)
186 .with_context(|| {
187 format!(
188 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
189 cargo_toml.display(),
190 cargo_version
191 )
192 })?;
193 let sysroot = if with_sysroot {
194 Sysroot::discover(&cargo_toml).with_context(|| {
195 format!(
196 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
197 cargo_toml.display()
198 )
199 })?
200 } else {
201 Sysroot::default()
202 };
203 ProjectWorkspace::Cargo { cargo, sysroot }
204 }
205 };
206
207 Ok(res)
208 }
209
210 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
211 let sysroot = match &project_json.sysroot_src {
212 Some(path) => Some(Sysroot::load(path)?),
213 None => None,
214 };
215
216 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
217 }
218
219 /// Returns the roots for the current `ProjectWorkspace`
220 /// The return type contains the path and whether or not
221 /// the root is a member of the current workspace
222 pub fn to_roots(&self) -> Vec<PackageRoot> {
223 match self {
224 ProjectWorkspace::Json { project, sysroot } => project
225 .crates()
226 .map(|(_, krate)| PackageRoot {
227 is_member: krate.is_workspace_member,
228 include: krate.include.clone(),
229 exclude: krate.exclude.clone(),
230 })
231 .collect::<FxHashSet<_>>()
232 .into_iter()
233 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
234 sysroot.crates().map(move |krate| PackageRoot {
235 is_member: false,
236 include: vec![sysroot[krate].root_dir().to_path_buf()],
237 exclude: Vec::new(),
238 })
239 }))
240 .collect::<Vec<_>>(),
241 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
242 .packages()
243 .map(|pkg| {
244 let is_member = cargo[pkg].is_member;
245 let pkg_root = cargo[pkg].root().to_path_buf();
246
247 let mut include = vec![pkg_root.clone()];
248 include.extend(cargo[pkg].out_dir.clone());
249
250 let mut exclude = vec![pkg_root.join(".git")];
251 if is_member {
252 exclude.push(pkg_root.join("target"));
253 } else {
254 exclude.push(pkg_root.join("tests"));
255 exclude.push(pkg_root.join("examples"));
256 exclude.push(pkg_root.join("benches"));
257 }
258 PackageRoot { is_member, include, exclude }
259 })
260 .chain(sysroot.crates().map(|krate| PackageRoot {
261 is_member: false,
262 include: vec![sysroot[krate].root_dir().to_path_buf()],
263 exclude: Vec::new(),
264 }))
265 .collect(),
266 }
267 }
268
269 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
270 match self {
271 ProjectWorkspace::Json { project, sysroot: _ } => project
272 .crates()
273 .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
274 .cloned()
275 .collect(),
276 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo
277 .packages()
278 .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
279 .cloned()
280 .collect(),
281 }
282 }
283
284 pub fn n_packages(&self) -> usize {
285 match self {
286 ProjectWorkspace::Json { project, .. } => project.n_crates(),
287 ProjectWorkspace::Cargo { cargo, sysroot } => {
288 cargo.packages().len() + sysroot.crates().len()
289 }
290 }
291 }
292
293 pub fn to_crate_graph(
294 &self,
295 target: Option<&str>,
296 proc_macro_client: &ProcMacroClient,
297 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
298 ) -> CrateGraph {
299 let mut crate_graph = CrateGraph::default();
300 match self {
301 ProjectWorkspace::Json { project, sysroot } => {
302 let sysroot_dps = sysroot
303 .as_ref()
304 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
305
306 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
307 let crates: FxHashMap<_, _> = project
308 .crates()
309 .filter_map(|(crate_id, krate)| {
310 let file_path = &krate.root_module;
311 let file_id = match load(&file_path) {
312 Some(id) => id,
313 None => {
314 log::error!("failed to load crate root {}", file_path.display());
315 return None;
316 }
317 };
318
319 let env = krate.env.clone().into_iter().collect();
320 let proc_macro = krate
321 .proc_macro_dylib_path
322 .clone()
323 .map(|it| proc_macro_client.by_dylib_path(&it));
324
325 let target = krate.target.as_deref().or(target);
326 let target_cfgs = cfg_cache
327 .entry(target)
328 .or_insert_with(|| get_rustc_cfg_options(target));
329
330 let mut cfg_options = CfgOptions::default();
331 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
332
333 Some((
334 crate_id,
335 crate_graph.add_crate_root(
336 file_id,
337 krate.edition,
338 krate.display_name.clone(),
339 cfg_options,
340 env,
341 proc_macro.unwrap_or_default(),
342 ),
343 ))
344 })
345 .collect();
346
347 for (from, krate) in project.crates() {
348 if let Some(&from) = crates.get(&from) {
349 if let Some((public_deps, _proc_macro)) = &sysroot_dps {
350 for (name, to) in public_deps.iter() {
351 if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) {
352 log::error!("cyclic dependency on {} for {:?}", name, from)
353 }
354 }
355 }
356
357 for dep in &krate.deps {
358 let to_crate_id = dep.crate_id;
359 if let Some(&to) = crates.get(&to_crate_id) {
360 if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
361 log::error!("cyclic dependency {:?} -> {:?}", from, to);
362 }
363 }
364 }
365 }
366 }
367 }
368 ProjectWorkspace::Cargo { cargo, sysroot } => {
369 let (public_deps, libproc_macro) =
370 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
371
372 let mut cfg_options = CfgOptions::default();
373 cfg_options.extend(get_rustc_cfg_options(target));
374
375 let mut pkg_to_lib_crate = FxHashMap::default();
376 let mut pkg_crates = FxHashMap::default();
377
378 // Add test cfg for non-sysroot crates
379 cfg_options.insert_atom("test".into());
380 cfg_options.insert_atom("debug_assertions".into());
381
382 // Next, create crates for each package, target pair
383 for pkg in cargo.packages() {
384 let mut lib_tgt = None;
385 for &tgt in cargo[pkg].targets.iter() {
386 let root = cargo[tgt].root.as_path();
387 if let Some(file_id) = load(root) {
388 let edition = cargo[pkg].edition;
389 let cfg_options = {
390 let mut opts = cfg_options.clone();
391 for feature in cargo[pkg].features.iter() {
392 opts.insert_key_value("feature".into(), feature.into());
393 }
394 opts.extend(cargo[pkg].cfgs.iter().cloned());
395 opts
396 };
397 let mut env = Env::default();
398 if let Some(out_dir) = &cargo[pkg].out_dir {
399 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
400 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
401 env.set("OUT_DIR", out_dir);
402 }
403 }
404 let proc_macro = cargo[pkg]
405 .proc_macro_dylib_path
406 .as_ref()
407 .map(|it| proc_macro_client.by_dylib_path(&it))
408 .unwrap_or_default();
409
410 let display_name =
411 CrateDisplayName::from_canonical_name(cargo[pkg].name.clone());
412 let crate_id = crate_graph.add_crate_root(
413 file_id,
414 edition,
415 Some(display_name),
416 cfg_options,
417 env,
418 proc_macro.clone(),
419 );
420 if cargo[tgt].kind == TargetKind::Lib {
421 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
422 pkg_to_lib_crate.insert(pkg, crate_id);
423 }
424 if cargo[tgt].is_proc_macro {
425 if let Some(proc_macro) = libproc_macro {
426 if let Err(_) = crate_graph.add_dep(
427 crate_id,
428 CrateName::new("proc_macro").unwrap(),
429 proc_macro,
430 ) {
431 log::error!(
432 "cyclic dependency on proc_macro for {}",
433 &cargo[pkg].name
434 )
435 }
436 }
437 }
438
439 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
440 }
441 }
442
443 // Set deps to the core, std and to the lib target of the current package
444 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
445 if let Some((to, name)) = lib_tgt.clone() {
446 // For root projects with dashes in their name,
447 // cargo metadata does not do any normalization,
448 // so we do it ourselves currently
449 let name = CrateName::normalize_dashes(&name);
450 if to != from && crate_graph.add_dep(from, name, to).is_err() {
451 log::error!(
452 "cyclic dependency between targets of {}",
453 &cargo[pkg].name
454 )
455 }
456 }
457 for (name, krate) in public_deps.iter() {
458 if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) {
459 log::error!(
460 "cyclic dependency on {} for {}",
461 name,
462 &cargo[pkg].name
463 )
464 }
465 }
466 }
467 }
468
469 // Now add a dep edge from all targets of upstream to the lib
470 // target of downstream.
471 for pkg in cargo.packages() {
472 for dep in cargo[pkg].dependencies.iter() {
473 let name = CrateName::new(&dep.name).unwrap();
474 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
475 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
476 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
477 log::error!(
478 "cyclic dependency {} -> {}",
479 &cargo[pkg].name,
480 &cargo[dep.pkg].name
481 )
482 }
483 }
484 }
485 }
486 }
487 }
488 }
489 if crate_graph.patch_cfg_if() {
490 log::debug!("Patched std to depend on cfg-if")
491 } else {
492 log::debug!("Did not patch std to depend on cfg-if")
493 }
494 crate_graph
495 }
496}
497
498fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
499 let mut res = Vec::new();
500
501 // Some nightly-only cfgs, which are required for stdlib
502 res.push(CfgFlag::Atom("target_thread_local".into()));
503 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
504 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
505 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
506 }
507 }
508
509 let rustc_cfgs = {
510 let mut cmd = Command::new(toolchain::rustc());
511 cmd.args(&["--print", "cfg", "-O"]);
512 if let Some(target) = target {
513 cmd.args(&["--target", target]);
514 }
515 utf8_stdout(cmd)
516 };
517
518 match rustc_cfgs {
519 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
520 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
521 }
522
523 res
524}
525
526fn utf8_stdout(mut cmd: Command) -> Result<String> { 114fn utf8_stdout(mut cmd: Command) -> Result<String> {
527 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; 115 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
528 if !output.status.success() { 116 if !output.status.success() {
@@ -536,52 +124,3 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> {
536 let stdout = String::from_utf8(output.stdout)?; 124 let stdout = String::from_utf8(output.stdout)?;
537 Ok(stdout.trim().to_string()) 125 Ok(stdout.trim().to_string())
538} 126}
539
540fn sysroot_to_crate_graph(
541 crate_graph: &mut CrateGraph,
542 sysroot: &Sysroot,
543 target: Option<&str>,
544 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
545) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
546 let mut cfg_options = CfgOptions::default();
547 cfg_options.extend(get_rustc_cfg_options(target));
548 let sysroot_crates: FxHashMap<_, _> = sysroot
549 .crates()
550 .filter_map(|krate| {
551 let file_id = load(&sysroot[krate].root)?;
552
553 let env = Env::default();
554 let proc_macro = vec![];
555 let name = CrateName::new(&sysroot[krate].name)
556 .expect("Sysroot crates' names do not contain dashes");
557 let crate_id = crate_graph.add_crate_root(
558 file_id,
559 Edition::Edition2018,
560 Some(name.into()),
561 cfg_options.clone(),
562 env,
563 proc_macro,
564 );
565 Some((krate, crate_id))
566 })
567 .collect();
568
569 for from in sysroot.crates() {
570 for &to in sysroot[from].deps.iter() {
571 let name = CrateName::new(&sysroot[to].name).unwrap();
572 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
573 if let Err(_) = crate_graph.add_dep(from, name, to) {
574 log::error!("cyclic dependency between sysroot crates")
575 }
576 }
577 }
578 }
579
580 let public_deps = sysroot
581 .public_deps()
582 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
583 .collect::<Vec<_>>();
584
585 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
586 (public_deps, libproc_macro)
587}
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index b0e8863f6..f0a43eaf6 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -37,7 +37,7 @@ impl Sysroot {
37 pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate)> + '_ { 37 pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate)> + '_ {
38 // core is added as a dependency before std in order to 38 // core is added as a dependency before std in order to
39 // mimic rustcs dependency order 39 // mimic rustcs dependency order
40 vec!["core", "alloc", "std"].into_iter().filter_map(move |it| Some((it, self.by_name(it)?))) 40 ["core", "alloc", "std"].iter().filter_map(move |&it| Some((it, self.by_name(it)?)))
41 } 41 }
42 42
43 pub fn proc_macro(&self) -> Option<SysrootCrate> { 43 pub fn proc_macro(&self) -> Option<SysrootCrate> {
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
new file mode 100644
index 000000000..a71f96164
--- /dev/null
+++ b/crates/project_model/src/workspace.rs
@@ -0,0 +1,552 @@
1//! Handles lowering of build-system specific workspace information (`cargo
2//! metadata` or `rust-project.json`) into representation stored in the salsa
3//! database -- `CrateGraph`.
4
5use std::{fmt, fs, path::Component, process::Command};
6
7use anyhow::{Context, Result};
8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
9use cfg::CfgOptions;
10use paths::{AbsPath, AbsPathBuf};
11use proc_macro_api::ProcMacroClient;
12use rustc_hash::{FxHashMap, FxHashSet};
13
14use crate::{
15 cargo_workspace, cfg_flag::CfgFlag, sysroot::SysrootCrate, utf8_stdout, CargoConfig,
16 CargoWorkspace, ProjectJson, ProjectManifest, Sysroot, TargetKind,
17};
18
19/// `PackageRoot` describes a package root folder.
20/// Which may be an external dependency, or a member of
21/// the current workspace.
22#[derive(Debug, Clone, Eq, PartialEq, Hash)]
23pub struct PackageRoot {
24 /// Is a member of the current workspace
25 pub is_member: bool,
26 pub include: Vec<AbsPathBuf>,
27 pub exclude: Vec<AbsPathBuf>,
28}
29
30#[derive(Clone, Eq, PartialEq)]
31pub enum ProjectWorkspace {
32 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
33 Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
34 /// Project workspace was manually specified using a `rust-project.json` file.
35 Json { project: ProjectJson, sysroot: Option<Sysroot> },
36}
37
38impl fmt::Debug for ProjectWorkspace {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
42 .debug_struct("Cargo")
43 .field("n_packages", &cargo.packages().len())
44 .field("n_sysroot_crates", &sysroot.crates().len())
45 .field(
46 "n_rustc_compiler_crates",
47 &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
48 )
49 .finish(),
50 ProjectWorkspace::Json { project, sysroot } => {
51 let mut debug_struct = f.debug_struct("Json");
52 debug_struct.field("n_crates", &project.n_crates());
53 if let Some(sysroot) = sysroot {
54 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
55 }
56 debug_struct.finish()
57 }
58 }
59 }
60}
61
62impl ProjectWorkspace {
63 pub fn load(manifest: ProjectManifest, config: &CargoConfig) -> Result<ProjectWorkspace> {
64 let res = match manifest {
65 ProjectManifest::ProjectJson(project_json) => {
66 let file = fs::read_to_string(&project_json).with_context(|| {
67 format!("Failed to read json file {}", project_json.display())
68 })?;
69 let data = serde_json::from_str(&file).with_context(|| {
70 format!("Failed to deserialize json file {}", project_json.display())
71 })?;
72 let project_location = project_json.parent().unwrap().to_path_buf();
73 let project_json = ProjectJson::new(&project_location, data);
74 ProjectWorkspace::load_inline(project_json)?
75 }
76 ProjectManifest::CargoToml(cargo_toml) => {
77 let cargo_version = utf8_stdout({
78 let mut cmd = Command::new(toolchain::cargo());
79 cmd.arg("--version");
80 cmd
81 })?;
82
83 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, config).with_context(
84 || {
85 format!(
86 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
87 cargo_toml.display(),
88 cargo_version
89 )
90 },
91 )?;
92 let sysroot = if config.no_sysroot {
93 Sysroot::default()
94 } else {
95 Sysroot::discover(&cargo_toml).with_context(|| {
96 format!(
97 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
98 cargo_toml.display()
99 )
100 })?
101 };
102
103 let rustc = if let Some(rustc_dir) = &config.rustc_source {
104 Some(CargoWorkspace::from_cargo_metadata(&rustc_dir, config).with_context(
105 || format!("Failed to read Cargo metadata for Rust sources"),
106 )?)
107 } else {
108 None
109 };
110
111 ProjectWorkspace::Cargo { cargo, sysroot, rustc }
112 }
113 };
114
115 Ok(res)
116 }
117
118 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
119 let sysroot = match &project_json.sysroot_src {
120 Some(path) => Some(Sysroot::load(path)?),
121 None => None,
122 };
123
124 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
125 }
126
127 /// Returns the roots for the current `ProjectWorkspace`
128 /// The return type contains the path and whether or not
129 /// the root is a member of the current workspace
130 pub fn to_roots(&self) -> Vec<PackageRoot> {
131 match self {
132 ProjectWorkspace::Json { project, sysroot } => project
133 .crates()
134 .map(|(_, krate)| PackageRoot {
135 is_member: krate.is_workspace_member,
136 include: krate.include.clone(),
137 exclude: krate.exclude.clone(),
138 })
139 .collect::<FxHashSet<_>>()
140 .into_iter()
141 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
142 sysroot.crates().map(move |krate| PackageRoot {
143 is_member: false,
144 include: vec![sysroot[krate].root_dir().to_path_buf()],
145 exclude: Vec::new(),
146 })
147 }))
148 .collect::<Vec<_>>(),
149 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => cargo
150 .packages()
151 .map(|pkg| {
152 let is_member = cargo[pkg].is_member;
153 let pkg_root = cargo[pkg].root().to_path_buf();
154
155 let mut include = vec![pkg_root.clone()];
156 include.extend(cargo[pkg].out_dir.clone());
157
158 let mut exclude = vec![pkg_root.join(".git")];
159 if is_member {
160 exclude.push(pkg_root.join("target"));
161 } else {
162 exclude.push(pkg_root.join("tests"));
163 exclude.push(pkg_root.join("examples"));
164 exclude.push(pkg_root.join("benches"));
165 }
166 PackageRoot { is_member, include, exclude }
167 })
168 .chain(sysroot.crates().map(|krate| PackageRoot {
169 is_member: false,
170 include: vec![sysroot[krate].root_dir().to_path_buf()],
171 exclude: Vec::new(),
172 }))
173 .chain(rustc.into_iter().flat_map(|rustc| {
174 rustc.packages().map(move |krate| PackageRoot {
175 is_member: false,
176 include: vec![rustc[krate].root().to_path_buf()],
177 exclude: Vec::new(),
178 })
179 }))
180 .collect(),
181 }
182 }
183
184 pub fn n_packages(&self) -> usize {
185 match self {
186 ProjectWorkspace::Json { project, .. } => project.n_crates(),
187 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
188 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
189 cargo.packages().len() + sysroot.crates().len() + rustc_package_len
190 }
191 }
192 }
193
194 pub fn to_crate_graph(
195 &self,
196 target: Option<&str>,
197 proc_macro_client: &ProcMacroClient,
198 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
199 ) -> CrateGraph {
200 let mut crate_graph = match self {
201 ProjectWorkspace::Json { project, sysroot } => {
202 project_json_to_crate_graph(target, proc_macro_client, load, project, sysroot)
203 }
204 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
205 cargo_to_crate_graph(target, proc_macro_client, load, cargo, sysroot, rustc)
206 }
207 };
208 if crate_graph.patch_cfg_if() {
209 log::debug!("Patched std to depend on cfg-if")
210 } else {
211 log::debug!("Did not patch std to depend on cfg-if")
212 }
213 crate_graph
214 }
215}
216
217fn project_json_to_crate_graph(
218 target: Option<&str>,
219 proc_macro_client: &ProcMacroClient,
220 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
221 project: &ProjectJson,
222 sysroot: &Option<Sysroot>,
223) -> CrateGraph {
224 let mut crate_graph = CrateGraph::default();
225 let sysroot_deps = sysroot
226 .as_ref()
227 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
228
229 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
230 let crates: FxHashMap<CrateId, CrateId> = project
231 .crates()
232 .filter_map(|(crate_id, krate)| {
233 let file_path = &krate.root_module;
234 let file_id = load(&file_path)?;
235 Some((crate_id, krate, file_id))
236 })
237 .map(|(crate_id, krate, file_id)| {
238 let env = krate.env.clone().into_iter().collect();
239 let proc_macro =
240 krate.proc_macro_dylib_path.clone().map(|it| proc_macro_client.by_dylib_path(&it));
241
242 let target = krate.target.as_deref().or(target);
243 let target_cfgs =
244 cfg_cache.entry(target).or_insert_with(|| get_rustc_cfg_options(target));
245
246 let mut cfg_options = CfgOptions::default();
247 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
248 (
249 crate_id,
250 crate_graph.add_crate_root(
251 file_id,
252 krate.edition,
253 krate.display_name.clone(),
254 cfg_options,
255 env,
256 proc_macro.unwrap_or_default(),
257 ),
258 )
259 })
260 .collect();
261
262 for (from, krate) in project.crates() {
263 if let Some(&from) = crates.get(&from) {
264 if let Some((public_deps, _proc_macro)) = &sysroot_deps {
265 for (name, to) in public_deps.iter() {
266 add_dep(&mut crate_graph, from, name.clone(), *to)
267 }
268 }
269
270 for dep in &krate.deps {
271 if let Some(&to) = crates.get(&dep.crate_id) {
272 add_dep(&mut crate_graph, from, dep.name.clone(), to)
273 }
274 }
275 }
276 }
277 crate_graph
278}
279
280fn cargo_to_crate_graph(
281 target: Option<&str>,
282 proc_macro_client: &ProcMacroClient,
283 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
284 cargo: &CargoWorkspace,
285 sysroot: &Sysroot,
286 rustc: &Option<CargoWorkspace>,
287) -> CrateGraph {
288 let mut crate_graph = CrateGraph::default();
289 let (public_deps, libproc_macro) =
290 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
291
292 let mut cfg_options = CfgOptions::default();
293 cfg_options.extend(get_rustc_cfg_options(target));
294
295 let mut pkg_to_lib_crate = FxHashMap::default();
296
297 // Add test cfg for non-sysroot crates
298 cfg_options.insert_atom("test".into());
299 cfg_options.insert_atom("debug_assertions".into());
300
301 let mut pkg_crates = FxHashMap::default();
302
303 // Next, create crates for each package, target pair
304 for pkg in cargo.packages() {
305 let mut lib_tgt = None;
306 for &tgt in cargo[pkg].targets.iter() {
307 if let Some(file_id) = load(&cargo[tgt].root) {
308 let crate_id = add_target_crate_root(
309 &mut crate_graph,
310 &cargo[pkg],
311 &cfg_options,
312 proc_macro_client,
313 file_id,
314 );
315 if cargo[tgt].kind == TargetKind::Lib {
316 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
317 pkg_to_lib_crate.insert(pkg, crate_id);
318 }
319 if cargo[tgt].is_proc_macro {
320 if let Some(proc_macro) = libproc_macro {
321 add_dep(
322 &mut crate_graph,
323 crate_id,
324 CrateName::new("proc_macro").unwrap(),
325 proc_macro,
326 );
327 }
328 }
329
330 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
331 }
332 }
333
334 // Set deps to the core, std and to the lib target of the current package
335 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
336 if let Some((to, name)) = lib_tgt.clone() {
337 if to != from {
338 // For root projects with dashes in their name,
339 // cargo metadata does not do any normalization,
340 // so we do it ourselves currently
341 let name = CrateName::normalize_dashes(&name);
342 add_dep(&mut crate_graph, from, name, to);
343 }
344 }
345 for (name, krate) in public_deps.iter() {
346 add_dep(&mut crate_graph, from, name.clone(), *krate);
347 }
348 }
349 }
350
351 // Now add a dep edge from all targets of upstream to the lib
352 // target of downstream.
353 for pkg in cargo.packages() {
354 for dep in cargo[pkg].dependencies.iter() {
355 let name = CrateName::new(&dep.name).unwrap();
356 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
357 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
358 add_dep(&mut crate_graph, from, name.clone(), to)
359 }
360 }
361 }
362 }
363
364 let mut rustc_pkg_crates = FxHashMap::default();
365
366 // If the user provided a path to rustc sources, we add all the rustc_private crates
367 // and create dependencies on them for the crates in the current workspace
368 if let Some(rustc_workspace) = rustc {
369 for pkg in rustc_workspace.packages() {
370 for &tgt in rustc_workspace[pkg].targets.iter() {
371 if rustc_workspace[tgt].kind != TargetKind::Lib {
372 continue;
373 }
374 // Exclude alloc / core / std
375 if rustc_workspace[tgt]
376 .root
377 .components()
378 .any(|c| c == Component::Normal("library".as_ref()))
379 {
380 continue;
381 }
382
383 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
384 let crate_id = add_target_crate_root(
385 &mut crate_graph,
386 &rustc_workspace[pkg],
387 &cfg_options,
388 proc_macro_client,
389 file_id,
390 );
391 pkg_to_lib_crate.insert(pkg, crate_id);
392 // Add dependencies on the core / std / alloc for rustc
393 for (name, krate) in public_deps.iter() {
394 add_dep(&mut crate_graph, crate_id, name.clone(), *krate);
395 }
396 rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
397 }
398 }
399 }
400 // Now add a dep edge from all targets of upstream to the lib
401 // target of downstream.
402 for pkg in rustc_workspace.packages() {
403 for dep in rustc_workspace[pkg].dependencies.iter() {
404 let name = CrateName::new(&dep.name).unwrap();
405 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
406 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
407 add_dep(&mut crate_graph, from, name.clone(), to);
408 }
409 }
410 }
411 }
412
413 // Add dependencies for all the crates of the current workspace to rustc_private libraries
414 for dep in rustc_workspace.packages() {
415 let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
416
417 if let Some(&to) = pkg_to_lib_crate.get(&dep) {
418 for pkg in cargo.packages() {
419 if !cargo[pkg].is_member {
420 continue;
421 }
422 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
423 add_dep(&mut crate_graph, from, name.clone(), to);
424 }
425 }
426 }
427 }
428 }
429 crate_graph
430}
431
432fn add_target_crate_root(
433 crate_graph: &mut CrateGraph,
434 pkg: &cargo_workspace::PackageData,
435 cfg_options: &CfgOptions,
436 proc_macro_client: &ProcMacroClient,
437 file_id: FileId,
438) -> CrateId {
439 let edition = pkg.edition;
440 let cfg_options = {
441 let mut opts = cfg_options.clone();
442 for feature in pkg.features.iter() {
443 opts.insert_key_value("feature".into(), feature.into());
444 }
445 opts.extend(pkg.cfgs.iter().cloned());
446 opts
447 };
448 let mut env = Env::default();
449 if let Some(out_dir) = &pkg.out_dir {
450 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
451 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
452 env.set("OUT_DIR", out_dir);
453 }
454 }
455 let proc_macro = pkg
456 .proc_macro_dylib_path
457 .as_ref()
458 .map(|it| proc_macro_client.by_dylib_path(&it))
459 .unwrap_or_default();
460
461 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
462 let crate_id = crate_graph.add_crate_root(
463 file_id,
464 edition,
465 Some(display_name),
466 cfg_options,
467 env,
468 proc_macro.clone(),
469 );
470
471 crate_id
472}
473
474fn sysroot_to_crate_graph(
475 crate_graph: &mut CrateGraph,
476 sysroot: &Sysroot,
477 target: Option<&str>,
478 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
479) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
480 let mut cfg_options = CfgOptions::default();
481 cfg_options.extend(get_rustc_cfg_options(target));
482 let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
483 .crates()
484 .filter_map(|krate| {
485 let file_id = load(&sysroot[krate].root)?;
486
487 let env = Env::default();
488 let proc_macro = vec![];
489 let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
490 let crate_id = crate_graph.add_crate_root(
491 file_id,
492 Edition::Edition2018,
493 Some(display_name),
494 cfg_options.clone(),
495 env,
496 proc_macro,
497 );
498 Some((krate, crate_id))
499 })
500 .collect();
501
502 for from in sysroot.crates() {
503 for &to in sysroot[from].deps.iter() {
504 let name = CrateName::new(&sysroot[to].name).unwrap();
505 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
506 add_dep(crate_graph, from, name, to);
507 }
508 }
509 }
510
511 let public_deps = sysroot
512 .public_deps()
513 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
514 .collect::<Vec<_>>();
515
516 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
517 (public_deps, libproc_macro)
518}
519
520fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
521 let mut res = Vec::new();
522
523 // Some nightly-only cfgs, which are required for stdlib
524 res.push(CfgFlag::Atom("target_thread_local".into()));
525 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
526 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
527 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
528 }
529 }
530
531 let rustc_cfgs = {
532 let mut cmd = Command::new(toolchain::rustc());
533 cmd.args(&["--print", "cfg", "-O"]);
534 if let Some(target) = target {
535 cmd.args(&["--target", target]);
536 }
537 utf8_stdout(cmd)
538 };
539
540 match rustc_cfgs {
541 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
542 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
543 }
544
545 res
546}
547
548fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
549 if let Err(err) = graph.add_dep(from, name, to) {
550 log::error!("{}", err)
551 }
552}
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index d25c4bf83..436f5041b 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -21,7 +21,7 @@ env_logger = { version = "0.8.1", default-features = false }
21itertools = "0.9.0" 21itertools = "0.9.0"
22jod-thread = "0.1.0" 22jod-thread = "0.1.0"
23log = "0.4.8" 23log = "0.4.8"
24lsp-types = { version = "0.83.0", features = ["proposed"] } 24lsp-types = { version = "0.84.0", features = ["proposed"] }
25parking_lot = "0.11.0" 25parking_lot = "0.11.0"
26pico-args = "0.3.1" 26pico-args = "0.3.1"
27oorandom = "11.1.2" 27oorandom = "11.1.2"
@@ -31,7 +31,7 @@ serde_json = "1.0.48"
31threadpool = "1.7.1" 31threadpool = "1.7.1"
32rayon = "1.5" 32rayon = "1.5"
33mimalloc = { version = "0.1.19", default-features = false, optional = true } 33mimalloc = { version = "0.1.19", default-features = false, optional = true }
34lsp-server = "0.4.0" 34lsp-server = "0.5.0"
35tracing = "0.1" 35tracing = "0.1"
36tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] } 36tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] }
37tracing-tree = { version = "0.1.4" } 37tracing-tree = { version = "0.1.4" }
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index ff1ae9575..c7203451c 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -16,8 +16,6 @@ use serde_json::json;
16use crate::semantic_tokens; 16use crate::semantic_tokens;
17 17
18pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities { 18pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities {
19 let code_action_provider = code_action_capabilities(client_caps);
20
21 ServerCapabilities { 19 ServerCapabilities {
22 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { 20 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
23 open_close: Some(true), 21 open_close: Some(true),
@@ -49,7 +47,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
49 document_highlight_provider: Some(OneOf::Left(true)), 47 document_highlight_provider: Some(OneOf::Left(true)),
50 document_symbol_provider: Some(OneOf::Left(true)), 48 document_symbol_provider: Some(OneOf::Left(true)),
51 workspace_symbol_provider: Some(OneOf::Left(true)), 49 workspace_symbol_provider: Some(OneOf::Left(true)),
52 code_action_provider: Some(code_action_provider), 50 code_action_provider: Some(code_action_capabilities(client_caps)),
53 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), 51 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
54 document_formatting_provider: Some(OneOf::Left(true)), 52 document_formatting_provider: Some(OneOf::Left(true)),
55 document_range_formatting_provider: None, 53 document_range_formatting_provider: None,
@@ -64,6 +62,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
64 prepare_provider: Some(true), 62 prepare_provider: Some(true),
65 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, 63 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
66 })), 64 })),
65 on_type_rename_provider: None,
67 document_link_provider: None, 66 document_link_provider: None,
68 color_provider: None, 67 color_provider: None,
69 execute_command_provider: None, 68 execute_command_provider: None,
@@ -113,7 +112,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
113 CodeActionKind::REFACTOR_INLINE, 112 CodeActionKind::REFACTOR_INLINE,
114 CodeActionKind::REFACTOR_REWRITE, 113 CodeActionKind::REFACTOR_REWRITE,
115 ]), 114 ]),
116 resolve_provider: None, 115 resolve_provider: Some(true),
117 work_done_progress_options: Default::default(), 116 work_done_progress_options: Default::default(),
118 }) 117 })
119 }) 118 })
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index ab1e2ab92..76526c66c 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -21,7 +21,6 @@ pub fn load_cargo(
21 let ws = ProjectWorkspace::load( 21 let ws = ProjectWorkspace::load(
22 root, 22 root,
23 &CargoConfig { load_out_dirs_from_check, ..Default::default() }, 23 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
24 true,
25 )?; 24 )?;
26 25
27 let (sender, receiver) = unbounded(); 26 let (sender, receiver) = unbounded();
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 2ed6a0d82..a334cdb11 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,7 +7,7 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use std::{ffi::OsString, path::PathBuf}; 10use std::{convert::TryFrom, ffi::OsString, path::PathBuf};
11 11
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use hir::PrefixKind; 13use hir::PrefixKind;
@@ -49,7 +49,6 @@ pub struct Config {
49 pub hover: HoverConfig, 49 pub hover: HoverConfig,
50 pub semantic_tokens_refresh: bool, 50 pub semantic_tokens_refresh: bool,
51 51
52 pub with_sysroot: bool,
53 pub linked_projects: Vec<LinkedProject>, 52 pub linked_projects: Vec<LinkedProject>,
54 pub root_path: AbsPathBuf, 53 pub root_path: AbsPathBuf,
55} 54}
@@ -144,7 +143,7 @@ pub struct ClientCapsConfig {
144 pub code_action_literals: bool, 143 pub code_action_literals: bool,
145 pub work_done_progress: bool, 144 pub work_done_progress: bool,
146 pub code_action_group: bool, 145 pub code_action_group: bool,
147 pub resolve_code_action: bool, 146 pub code_action_resolve: bool,
148 pub hover_actions: bool, 147 pub hover_actions: bool,
149 pub status_notification: bool, 148 pub status_notification: bool,
150 pub signature_help_label_offsets: bool, 149 pub signature_help_label_offsets: bool,
@@ -155,7 +154,6 @@ impl Config {
155 Config { 154 Config {
156 client_caps: ClientCapsConfig::default(), 155 client_caps: ClientCapsConfig::default(),
157 156
158 with_sysroot: true,
159 publish_diagnostics: true, 157 publish_diagnostics: true,
160 diagnostics: DiagnosticsConfig::default(), 158 diagnostics: DiagnosticsConfig::default(),
161 diagnostics_map: DiagnosticsMapConfig::default(), 159 diagnostics_map: DiagnosticsMapConfig::default(),
@@ -186,6 +184,7 @@ impl Config {
186 }, 184 },
187 completion: CompletionConfig { 185 completion: CompletionConfig {
188 enable_postfix_completions: true, 186 enable_postfix_completions: true,
187 enable_experimental_completions: true,
189 add_call_parenthesis: true, 188 add_call_parenthesis: true,
190 add_call_argument_snippets: true, 189 add_call_argument_snippets: true,
191 ..CompletionConfig::default() 190 ..CompletionConfig::default()
@@ -209,7 +208,6 @@ impl Config {
209 208
210 let data = ConfigData::from_json(json); 209 let data = ConfigData::from_json(json);
211 210
212 self.with_sysroot = data.withSysroot;
213 self.publish_diagnostics = data.diagnostics_enable; 211 self.publish_diagnostics = data.diagnostics_enable;
214 self.diagnostics = DiagnosticsConfig { 212 self.diagnostics = DiagnosticsConfig {
215 disable_experimental: !data.diagnostics_enableExperimental, 213 disable_experimental: !data.diagnostics_enableExperimental,
@@ -227,12 +225,26 @@ impl Config {
227 self.notifications = 225 self.notifications =
228 NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound }; 226 NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound };
229 self.cargo_autoreload = data.cargo_autoreload; 227 self.cargo_autoreload = data.cargo_autoreload;
228
229 let rustc_source = if let Some(rustc_source) = data.rustcSource {
230 let rustpath: PathBuf = rustc_source.into();
231 AbsPathBuf::try_from(rustpath)
232 .map_err(|_| {
233 log::error!("rustc source directory must be an absolute path");
234 })
235 .ok()
236 } else {
237 None
238 };
239
230 self.cargo = CargoConfig { 240 self.cargo = CargoConfig {
231 no_default_features: data.cargo_noDefaultFeatures, 241 no_default_features: data.cargo_noDefaultFeatures,
232 all_features: data.cargo_allFeatures, 242 all_features: data.cargo_allFeatures,
233 features: data.cargo_features.clone(), 243 features: data.cargo_features.clone(),
234 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck, 244 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
235 target: data.cargo_target.clone(), 245 target: data.cargo_target.clone(),
246 rustc_source: rustc_source,
247 no_sysroot: data.cargo_noSysroot,
236 }; 248 };
237 self.runnables = RunnablesConfig { 249 self.runnables = RunnablesConfig {
238 override_cargo: data.runnables_overrideCargo, 250 override_cargo: data.runnables_overrideCargo,
@@ -283,10 +295,6 @@ impl Config {
283 max_length: data.inlayHints_maxLength, 295 max_length: data.inlayHints_maxLength,
284 }; 296 };
285 297
286 self.completion.enable_postfix_completions = data.completion_postfix_enable;
287 self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
288 self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
289
290 self.assist.insert_use.merge = match data.assist_importMergeBehaviour { 298 self.assist.insert_use.merge = match data.assist_importMergeBehaviour {
291 MergeBehaviourDef::None => None, 299 MergeBehaviourDef::None => None,
292 MergeBehaviourDef::Full => Some(MergeBehaviour::Full), 300 MergeBehaviourDef::Full => Some(MergeBehaviour::Full),
@@ -298,6 +306,12 @@ impl Config {
298 ImportPrefixDef::BySelf => PrefixKind::BySelf, 306 ImportPrefixDef::BySelf => PrefixKind::BySelf,
299 }; 307 };
300 308
309 self.completion.enable_postfix_completions = data.completion_postfix_enable;
310 self.completion.enable_experimental_completions = data.completion_enableExperimental;
311 self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
312 self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
313 self.completion.merge = self.assist.insert_use.merge;
314
301 self.call_info_full = data.callInfo_full; 315 self.call_info_full = data.callInfo_full;
302 316
303 self.lens = LensConfig { 317 self.lens = LensConfig {
@@ -383,6 +397,14 @@ impl Config {
383 } 397 }
384 } 398 }
385 } 399 }
400
401 if let Some(code_action) = &doc_caps.code_action {
402 if let Some(resolve_support) = &code_action.resolve_support {
403 if resolve_support.properties.iter().any(|it| it == "edit") {
404 self.client_caps.code_action_resolve = true;
405 }
406 }
407 }
386 } 408 }
387 409
388 if let Some(window_caps) = caps.window.as_ref() { 410 if let Some(window_caps) = caps.window.as_ref() {
@@ -400,7 +422,6 @@ impl Config {
400 self.assist.allow_snippets(snippet_text_edit); 422 self.assist.allow_snippets(snippet_text_edit);
401 423
402 self.client_caps.code_action_group = get_bool("codeActionGroup"); 424 self.client_caps.code_action_group = get_bool("codeActionGroup");
403 self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
404 self.client_caps.hover_actions = get_bool("hoverActions"); 425 self.client_caps.hover_actions = get_bool("hoverActions");
405 self.client_caps.status_notification = get_bool("statusNotification"); 426 self.client_caps.status_notification = get_bool("statusNotification");
406 } 427 }
@@ -472,6 +493,7 @@ config_data! {
472 cargo_loadOutDirsFromCheck: bool = false, 493 cargo_loadOutDirsFromCheck: bool = false,
473 cargo_noDefaultFeatures: bool = false, 494 cargo_noDefaultFeatures: bool = false,
474 cargo_target: Option<String> = None, 495 cargo_target: Option<String> = None,
496 cargo_noSysroot: bool = false,
475 497
476 checkOnSave_enable: bool = true, 498 checkOnSave_enable: bool = true,
477 checkOnSave_allFeatures: Option<bool> = None, 499 checkOnSave_allFeatures: Option<bool> = None,
@@ -486,6 +508,7 @@ config_data! {
486 completion_addCallArgumentSnippets: bool = true, 508 completion_addCallArgumentSnippets: bool = true,
487 completion_addCallParenthesis: bool = true, 509 completion_addCallParenthesis: bool = true,
488 completion_postfix_enable: bool = true, 510 completion_postfix_enable: bool = true,
511 completion_enableExperimental: bool = true,
489 512
490 diagnostics_enable: bool = true, 513 diagnostics_enable: bool = true,
491 diagnostics_enableExperimental: bool = true, 514 diagnostics_enableExperimental: bool = true,
@@ -524,6 +547,6 @@ config_data! {
524 rustfmt_extraArgs: Vec<String> = Vec::new(), 547 rustfmt_extraArgs: Vec<String> = Vec::new(),
525 rustfmt_overrideCommand: Option<Vec<String>> = None, 548 rustfmt_overrideCommand: Option<Vec<String>> = None,
526 549
527 withSysroot: bool = true, 550 rustcSource : Option<String> = None,
528 } 551 }
529} 552}
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
index 58d47d32a..72f6c5725 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/compiler/mir/tagset.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/compiler/mir/tagset.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -20,7 +27,24 @@
20 "trivially_copy_pass_by_ref", 27 "trivially_copy_pass_by_ref",
21 ), 28 ),
22 ), 29 ),
23 code_description: None, 30 code_description: Some(
31 CodeDescription {
32 href: Url {
33 scheme: "https",
34 host: Some(
35 Domain(
36 "rust-lang.github.io",
37 ),
38 ),
39 port: None,
40 path: "/rust-clippy/master/index.html",
41 query: None,
42 fragment: Some(
43 "trivially_copy_pass_by_ref",
44 ),
45 },
46 },
47 ),
24 source: Some( 48 source: Some(
25 "clippy", 49 "clippy",
26 ), 50 ),
@@ -29,7 +53,14 @@
29 [ 53 [
30 DiagnosticRelatedInformation { 54 DiagnosticRelatedInformation {
31 location: Location { 55 location: Location {
32 uri: "file:///test/compiler/lib.rs", 56 uri: Url {
57 scheme: "file",
58 host: None,
59 port: None,
60 path: "/test/compiler/lib.rs",
61 query: None,
62 fragment: None,
63 },
33 range: Range { 64 range: Range {
34 start: Position { 65 start: Position {
35 line: 0, 66 line: 0,
@@ -45,7 +76,14 @@
45 }, 76 },
46 DiagnosticRelatedInformation { 77 DiagnosticRelatedInformation {
47 location: Location { 78 location: Location {
48 uri: "file:///test/compiler/mir/tagset.rs", 79 uri: Url {
80 scheme: "file",
81 host: None,
82 port: None,
83 path: "/test/compiler/mir/tagset.rs",
84 query: None,
85 fragment: None,
86 },
49 range: Range { 87 range: Range {
50 start: Position { 88 start: Position {
51 line: 41, 89 line: 41,
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt b/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt
index 6aa26bf63..eb4a6b597 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/src/main.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/src/main.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -20,7 +27,24 @@
20 "E0277", 27 "E0277",
21 ), 28 ),
22 ), 29 ),
23 code_description: None, 30 code_description: Some(
31 CodeDescription {
32 href: Url {
33 scheme: "https",
34 host: Some(
35 Domain(
36 "doc.rust-lang.org",
37 ),
38 ),
39 port: None,
40 path: "/error-index.html",
41 query: None,
42 fragment: Some(
43 "E0277",
44 ),
45 },
46 },
47 ),
24 source: Some( 48 source: Some(
25 "rustc", 49 "rustc",
26 ), 50 ),
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index 7aaffaba2..bbec6a796 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/crates/hir_def/src/data.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/crates/hir_def/src/data.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -25,7 +32,14 @@
25 [ 32 [
26 DiagnosticRelatedInformation { 33 DiagnosticRelatedInformation {
27 location: Location { 34 location: Location {
28 uri: "file:///test/crates/hir_def/src/path.rs", 35 uri: Url {
36 scheme: "file",
37 host: None,
38 port: None,
39 path: "/test/crates/hir_def/src/path.rs",
40 query: None,
41 fragment: None,
42 },
29 range: Range { 43 range: Range {
30 start: Position { 44 start: Position {
31 line: 264, 45 line: 264,
@@ -47,7 +61,14 @@
47 fixes: [], 61 fixes: [],
48 }, 62 },
49 MappedRustDiagnostic { 63 MappedRustDiagnostic {
50 url: "file:///test/crates/hir_def/src/path.rs", 64 url: Url {
65 scheme: "file",
66 host: None,
67 port: None,
68 path: "/test/crates/hir_def/src/path.rs",
69 query: None,
70 fragment: None,
71 },
51 diagnostic: Diagnostic { 72 diagnostic: Diagnostic {
52 range: Range { 73 range: Range {
53 start: Position { 74 start: Position {
@@ -72,7 +93,14 @@
72 [ 93 [
73 DiagnosticRelatedInformation { 94 DiagnosticRelatedInformation {
74 location: Location { 95 location: Location {
75 uri: "file:///test/crates/hir_def/src/data.rs", 96 uri: Url {
97 scheme: "file",
98 host: None,
99 port: None,
100 path: "/test/crates/hir_def/src/data.rs",
101 query: None,
102 fragment: None,
103 },
76 range: Range { 104 range: Range {
77 start: Position { 105 start: Position {
78 line: 79, 106 line: 79,
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt
index 584213420..19f72196d 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/compiler/ty/list_iter.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/compiler/ty/list_iter.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -20,7 +27,24 @@
20 "E0053", 27 "E0053",
21 ), 28 ),
22 ), 29 ),
23 code_description: None, 30 code_description: Some(
31 CodeDescription {
32 href: Url {
33 scheme: "https",
34 host: Some(
35 Domain(
36 "doc.rust-lang.org",
37 ),
38 ),
39 port: None,
40 path: "/error-index.html",
41 query: None,
42 fragment: Some(
43 "E0053",
44 ),
45 },
46 },
47 ),
24 source: Some( 48 source: Some(
25 "rustc", 49 "rustc",
26 ), 50 ),
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt
index 2610e4e20..15ac95d72 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/runtime/compiler_support.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/runtime/compiler_support.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -20,7 +27,24 @@
20 "E0308", 27 "E0308",
21 ), 28 ),
22 ), 29 ),
23 code_description: None, 30 code_description: Some(
31 CodeDescription {
32 href: Url {
33 scheme: "https",
34 host: Some(
35 Domain(
36 "doc.rust-lang.org",
37 ),
38 ),
39 port: None,
40 path: "/error-index.html",
41 query: None,
42 fragment: Some(
43 "E0308",
44 ),
45 },
46 },
47 ),
24 source: Some( 48 source: Some(
25 "rustc", 49 "rustc",
26 ), 50 ),
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
index 8dc53391e..c709de95f 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/driver/subcommand/repl.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/driver/subcommand/repl.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -36,7 +43,6 @@
36 fixes: [ 43 fixes: [
37 CodeAction { 44 CodeAction {
38 title: "consider prefixing with an underscore", 45 title: "consider prefixing with an underscore",
39 id: None,
40 group: None, 46 group: None,
41 kind: Some( 47 kind: Some(
42 CodeActionKind( 48 CodeActionKind(
@@ -47,7 +53,14 @@
47 SnippetWorkspaceEdit { 53 SnippetWorkspaceEdit {
48 changes: Some( 54 changes: Some(
49 { 55 {
50 "file:///test/driver/subcommand/repl.rs": [ 56 Url {
57 scheme: "file",
58 host: None,
59 port: None,
60 path: "/test/driver/subcommand/repl.rs",
61 query: None,
62 fragment: None,
63 }: [
51 TextEdit { 64 TextEdit {
52 range: Range { 65 range: Range {
53 start: Position { 66 start: Position {
@@ -70,6 +83,7 @@
70 is_preferred: Some( 83 is_preferred: Some(
71 true, 84 true,
72 ), 85 ),
86 data: None,
73 }, 87 },
74 ], 88 ],
75 }, 89 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
index c8703194c..632f438d7 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/driver/subcommand/repl.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/driver/subcommand/repl.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -36,7 +43,6 @@
36 fixes: [ 43 fixes: [
37 CodeAction { 44 CodeAction {
38 title: "consider prefixing with an underscore", 45 title: "consider prefixing with an underscore",
39 id: None,
40 group: None, 46 group: None,
41 kind: Some( 47 kind: Some(
42 CodeActionKind( 48 CodeActionKind(
@@ -47,7 +53,14 @@
47 SnippetWorkspaceEdit { 53 SnippetWorkspaceEdit {
48 changes: Some( 54 changes: Some(
49 { 55 {
50 "file:///test/driver/subcommand/repl.rs": [ 56 Url {
57 scheme: "file",
58 host: None,
59 port: None,
60 path: "/test/driver/subcommand/repl.rs",
61 query: None,
62 fragment: None,
63 }: [
51 TextEdit { 64 TextEdit {
52 range: Range { 65 range: Range {
53 start: Position { 66 start: Position {
@@ -70,6 +83,7 @@
70 is_preferred: Some( 83 is_preferred: Some(
71 true, 84 true,
72 ), 85 ),
86 data: None,
73 }, 87 },
74 ], 88 ],
75 }, 89 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
index dc93227ad..c0b79428d 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/driver/subcommand/repl.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/driver/subcommand/repl.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -36,7 +43,6 @@
36 fixes: [ 43 fixes: [
37 CodeAction { 44 CodeAction {
38 title: "consider prefixing with an underscore", 45 title: "consider prefixing with an underscore",
39 id: None,
40 group: None, 46 group: None,
41 kind: Some( 47 kind: Some(
42 CodeActionKind( 48 CodeActionKind(
@@ -47,7 +53,14 @@
47 SnippetWorkspaceEdit { 53 SnippetWorkspaceEdit {
48 changes: Some( 54 changes: Some(
49 { 55 {
50 "file:///test/driver/subcommand/repl.rs": [ 56 Url {
57 scheme: "file",
58 host: None,
59 port: None,
60 path: "/test/driver/subcommand/repl.rs",
61 query: None,
62 fragment: None,
63 }: [
51 TextEdit { 64 TextEdit {
52 range: Range { 65 range: Range {
53 start: Position { 66 start: Position {
@@ -70,6 +83,7 @@
70 is_preferred: Some( 83 is_preferred: Some(
71 true, 84 true,
72 ), 85 ),
86 data: None,
73 }, 87 },
74 ], 88 ],
75 }, 89 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
index ba1b98b33..b9650f3e4 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/compiler/ty/select.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/compiler/ty/select.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -20,7 +27,24 @@
20 "E0061", 27 "E0061",
21 ), 28 ),
22 ), 29 ),
23 code_description: None, 30 code_description: Some(
31 CodeDescription {
32 href: Url {
33 scheme: "https",
34 host: Some(
35 Domain(
36 "doc.rust-lang.org",
37 ),
38 ),
39 port: None,
40 path: "/error-index.html",
41 query: None,
42 fragment: Some(
43 "E0061",
44 ),
45 },
46 },
47 ),
24 source: Some( 48 source: Some(
25 "rustc", 49 "rustc",
26 ), 50 ),
@@ -29,7 +53,14 @@
29 [ 53 [
30 DiagnosticRelatedInformation { 54 DiagnosticRelatedInformation {
31 location: Location { 55 location: Location {
32 uri: "file:///test/compiler/ty/select.rs", 56 uri: Url {
57 scheme: "file",
58 host: None,
59 port: None,
60 path: "/test/compiler/ty/select.rs",
61 query: None,
62 fragment: None,
63 },
33 range: Range { 64 range: Range {
34 start: Position { 65 start: Position {
35 line: 218, 66 line: 218,
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
index 81f752672..c45f68a91 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
@@ -1,6 +1,13 @@
1[ 1[
2 MappedRustDiagnostic { 2 MappedRustDiagnostic {
3 url: "file:///test/src/main.rs", 3 url: Url {
4 scheme: "file",
5 host: None,
6 port: None,
7 path: "/test/src/main.rs",
8 query: None,
9 fragment: None,
10 },
4 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
5 range: Range { 12 range: Range {
6 start: Position { 13 start: Position {
@@ -20,7 +27,24 @@
20 "let_and_return", 27 "let_and_return",
21 ), 28 ),
22 ), 29 ),
23 code_description: None, 30 code_description: Some(
31 CodeDescription {
32 href: Url {
33 scheme: "https",
34 host: Some(
35 Domain(
36 "rust-lang.github.io",
37 ),
38 ),
39 port: None,
40 path: "/rust-clippy/master/index.html",
41 query: None,
42 fragment: Some(
43 "let_and_return",
44 ),
45 },
46 },
47 ),
24 source: Some( 48 source: Some(
25 "clippy", 49 "clippy",
26 ), 50 ),
@@ -29,7 +53,14 @@
29 [ 53 [
30 DiagnosticRelatedInformation { 54 DiagnosticRelatedInformation {
31 location: Location { 55 location: Location {
32 uri: "file:///test/src/main.rs", 56 uri: Url {
57 scheme: "file",
58 host: None,
59 port: None,
60 path: "/test/src/main.rs",
61 query: None,
62 fragment: None,
63 },
33 range: Range { 64 range: Range {
34 start: Position { 65 start: Position {
35 line: 2, 66 line: 2,
@@ -51,7 +82,6 @@
51 fixes: [ 82 fixes: [
52 CodeAction { 83 CodeAction {
53 title: "return the expression directly", 84 title: "return the expression directly",
54 id: None,
55 group: None, 85 group: None,
56 kind: Some( 86 kind: Some(
57 CodeActionKind( 87 CodeActionKind(
@@ -62,7 +92,14 @@
62 SnippetWorkspaceEdit { 92 SnippetWorkspaceEdit {
63 changes: Some( 93 changes: Some(
64 { 94 {
65 "file:///test/src/main.rs": [ 95 Url {
96 scheme: "file",
97 host: None,
98 port: None,
99 path: "/test/src/main.rs",
100 query: None,
101 fragment: None,
102 }: [
66 TextEdit { 103 TextEdit {
67 range: Range { 104 range: Range {
68 start: Position { 105 start: Position {
@@ -98,6 +135,7 @@
98 is_preferred: Some( 135 is_preferred: Some(
99 true, 136 true,
100 ), 137 ),
138 data: None,
101 }, 139 },
102 ], 140 ],
103 }, 141 },
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index b949577c1..324019614 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -55,8 +55,8 @@ fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Lo
55 55
56 // FIXME: this doesn't handle UTF16 offsets correctly 56 // FIXME: this doesn't handle UTF16 offsets correctly
57 let range = lsp_types::Range::new( 57 let range = lsp_types::Range::new(
58 lsp_types::Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), 58 lsp_types::Position::new(span.line_start as u32 - 1, span.column_start as u32 - 1),
59 lsp_types::Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), 59 lsp_types::Position::new(span.line_end as u32 - 1, span.column_end as u32 - 1),
60 ); 60 );
61 61
62 lsp_types::Location { uri, range } 62 lsp_types::Location { uri, range }
@@ -110,7 +110,6 @@ fn map_rust_child_diagnostic(
110 } else { 110 } else {
111 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { 111 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction {
112 title: rd.message.clone(), 112 title: rd.message.clone(),
113 id: None,
114 group: None, 113 group: None,
115 kind: Some(lsp_types::CodeActionKind::QUICKFIX), 114 kind: Some(lsp_types::CodeActionKind::QUICKFIX),
116 edit: Some(lsp_ext::SnippetWorkspaceEdit { 115 edit: Some(lsp_ext::SnippetWorkspaceEdit {
@@ -119,6 +118,7 @@ fn map_rust_child_diagnostic(
119 document_changes: None, 118 document_changes: None,
120 }), 119 }),
121 is_preferred: Some(true), 120 is_preferred: Some(true),
121 data: None,
122 }) 122 })
123 } 123 }
124} 124}
@@ -211,6 +211,12 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
211 } 211 }
212 } 212 }
213 213
214 let code_description = match source.as_str() {
215 "rustc" => rustc_code_description(code.as_deref()),
216 "clippy" => clippy_code_description(code.as_deref()),
217 _ => None,
218 };
219
214 primary_spans 220 primary_spans
215 .iter() 221 .iter()
216 .map(|primary_span| { 222 .map(|primary_span| {
@@ -248,7 +254,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
248 range: in_macro_location.range, 254 range: in_macro_location.range,
249 severity, 255 severity,
250 code: code.clone().map(lsp_types::NumberOrString::String), 256 code: code.clone().map(lsp_types::NumberOrString::String),
251 code_description: None, 257 code_description: code_description.clone(),
252 source: Some(source.clone()), 258 source: Some(source.clone()),
253 message: message.clone(), 259 message: message.clone(),
254 related_information: Some(information_for_additional_diagnostic), 260 related_information: Some(information_for_additional_diagnostic),
@@ -269,7 +275,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
269 range: location.range, 275 range: location.range,
270 severity, 276 severity,
271 code: code.clone().map(lsp_types::NumberOrString::String), 277 code: code.clone().map(lsp_types::NumberOrString::String),
272 code_description: None, 278 code_description: code_description.clone(),
273 source: Some(source.clone()), 279 source: Some(source.clone()),
274 message, 280 message,
275 related_information: if related_information.is_empty() { 281 related_information: if related_information.is_empty() {
@@ -292,6 +298,31 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
292 .collect() 298 .collect()
293} 299}
294 300
301fn rustc_code_description(code: Option<&str>) -> Option<lsp_types::CodeDescription> {
302 code.filter(|code| {
303 let mut chars = code.chars();
304 chars.next().map_or(false, |c| c == 'E')
305 && chars.by_ref().take(4).all(|c| c.is_ascii_digit())
306 && chars.next().is_none()
307 })
308 .and_then(|code| {
309 lsp_types::Url::parse(&format!("https://doc.rust-lang.org/error-index.html#{}", code))
310 .ok()
311 .map(|href| lsp_types::CodeDescription { href })
312 })
313}
314
315fn clippy_code_description(code: Option<&str>) -> Option<lsp_types::CodeDescription> {
316 code.and_then(|code| {
317 lsp_types::Url::parse(&format!(
318 "https://rust-lang.github.io/rust-clippy/master/index.html#{}",
319 code
320 ))
321 .ok()
322 .map(|href| lsp_types::CodeDescription { href })
323 })
324}
325
295#[cfg(test)] 326#[cfg(test)]
296#[cfg(not(windows))] 327#[cfg(not(windows))]
297mod tests { 328mod tests {
diff --git a/crates/rust-analyzer/src/document.rs b/crates/rust-analyzer/src/document.rs
index 04c7ee150..cf091510f 100644
--- a/crates/rust-analyzer/src/document.rs
+++ b/crates/rust-analyzer/src/document.rs
@@ -6,11 +6,11 @@
6/// client notifications. 6/// client notifications.
7#[derive(Debug, Clone)] 7#[derive(Debug, Clone)]
8pub(crate) struct DocumentData { 8pub(crate) struct DocumentData {
9 pub(crate) version: Option<i64>, 9 pub(crate) version: i32,
10} 10}
11 11
12impl DocumentData { 12impl DocumentData {
13 pub(crate) fn new(version: i64) -> Self { 13 pub(crate) fn new(version: i32) -> Self {
14 DocumentData { version: Some(version) } 14 DocumentData { version }
15 } 15 }
16} 16}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 63c70a09d..defe11c55 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -263,9 +263,9 @@ impl GlobalStateSnapshot {
263 self.vfs.read().1[&id] 263 self.vfs.read().1[&id]
264 } 264 }
265 265
266 pub(crate) fn url_file_version(&self, url: &Url) -> Option<i64> { 266 pub(crate) fn url_file_version(&self, url: &Url) -> Option<i32> {
267 let path = from_proto::vfs_path(&url).ok()?; 267 let path = from_proto::vfs_path(&url).ok()?;
268 self.mem_docs.get(&path)?.version 268 Some(self.mem_docs.get(&path)?.version)
269 } 269 }
270 270
271 pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url { 271 pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 049c583a4..1cf4139d2 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -18,7 +18,7 @@ use lsp_types::{
18 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, 18 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
19 CodeActionKind, CodeLens, Command, CompletionItem, Diagnostic, DiagnosticTag, 19 CodeActionKind, CodeLens, Command, CompletionItem, Diagnostic, DiagnosticTag,
20 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, 20 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
21 HoverContents, Location, Position, PrepareRenameResponse, Range, RenameParams, 21 HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams,
22 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams, 22 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
23 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, 23 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
24 SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, 24 SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
@@ -573,7 +573,8 @@ pub(crate) fn handle_completion(
573 .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item)) 573 .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item))
574 .collect(); 574 .collect();
575 575
576 Ok(Some(items.into())) 576 let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
577 Ok(Some(completion_list.into()))
577} 578}
578 579
579pub(crate) fn handle_folding_range( 580pub(crate) fn handle_folding_range(
@@ -806,11 +807,11 @@ fn handle_fixes(
806 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; 807 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
807 let action = lsp_ext::CodeAction { 808 let action = lsp_ext::CodeAction {
808 title: fix.label.to_string(), 809 title: fix.label.to_string(),
809 id: None,
810 group: None, 810 group: None,
811 kind: Some(CodeActionKind::QUICKFIX), 811 kind: Some(CodeActionKind::QUICKFIX),
812 edit: Some(edit), 812 edit: Some(edit),
813 is_preferred: Some(false), 813 is_preferred: Some(false),
814 data: None,
814 }; 815 };
815 res.push(action); 816 res.push(action);
816 } 817 }
@@ -852,11 +853,11 @@ pub(crate) fn handle_code_action(
852 853
853 handle_fixes(&snap, &params, &mut res)?; 854 handle_fixes(&snap, &params, &mut res)?;
854 855
855 if snap.config.client_caps.resolve_code_action { 856 if snap.config.client_caps.code_action_resolve {
856 for (index, assist) in 857 for (index, assist) in
857 snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() 858 snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
858 { 859 {
859 res.push(to_proto::unresolved_code_action(&snap, assist, index)?); 860 res.push(to_proto::unresolved_code_action(&snap, params.clone(), assist, index)?);
860 } 861 }
861 } else { 862 } else {
862 for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() { 863 for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() {
@@ -867,11 +868,16 @@ pub(crate) fn handle_code_action(
867 Ok(Some(res)) 868 Ok(Some(res))
868} 869}
869 870
870pub(crate) fn handle_resolve_code_action( 871pub(crate) fn handle_code_action_resolve(
871 mut snap: GlobalStateSnapshot, 872 mut snap: GlobalStateSnapshot,
872 params: lsp_ext::ResolveCodeActionParams, 873 mut code_action: lsp_ext::CodeAction,
873) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> { 874) -> Result<lsp_ext::CodeAction> {
874 let _p = profile::span("handle_resolve_code_action"); 875 let _p = profile::span("handle_code_action_resolve");
876 let params = match code_action.data.take() {
877 Some(it) => it,
878 None => Err("can't resolve code action without data")?,
879 };
880
875 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?; 881 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
876 let line_index = snap.analysis.file_line_index(file_id)?; 882 let line_index = snap.analysis.file_line_index(file_id)?;
877 let range = from_proto::text_range(&line_index, params.code_action_params.range); 883 let range = from_proto::text_range(&line_index, params.code_action_params.range);
@@ -888,7 +894,9 @@ pub(crate) fn handle_resolve_code_action(
888 let index = index.parse::<usize>().unwrap(); 894 let index = index.parse::<usize>().unwrap();
889 let assist = &assists[index]; 895 let assist = &assists[index];
890 assert!(assist.assist.id.0 == id); 896 assert!(assist.assist.id.0 == id);
891 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit) 897 let edit = to_proto::resolved_code_action(&snap, assist.clone())?.edit;
898 code_action.edit = edit;
899 Ok(code_action)
892} 900}
893 901
894pub(crate) fn handle_code_lens( 902pub(crate) fn handle_code_lens(
@@ -1120,8 +1128,15 @@ pub(crate) fn publish_diagnostics(
1120 .map(|d| Diagnostic { 1128 .map(|d| Diagnostic {
1121 range: to_proto::range(&line_index, d.range), 1129 range: to_proto::range(&line_index, d.range),
1122 severity: Some(to_proto::diagnostic_severity(d.severity)), 1130 severity: Some(to_proto::diagnostic_severity(d.severity)),
1123 code: None, 1131 code: d.code.map(|d| d.as_str().to_owned()).map(NumberOrString::String),
1124 code_description: None, 1132 code_description: d.code.and_then(|code| {
1133 lsp_types::Url::parse(&format!(
1134 "https://rust-analyzer.github.io/manual.html#{}",
1135 code.as_str()
1136 ))
1137 .ok()
1138 .map(|href| lsp_types::CodeDescription { href })
1139 }),
1125 source: Some("rust-analyzer".to_string()), 1140 source: Some("rust-analyzer".to_string()),
1126 message: d.message, 1141 message: d.message,
1127 related_information: None, 1142 related_information: None,
@@ -1315,6 +1330,28 @@ pub(crate) fn handle_open_docs(
1315 Ok(remote.and_then(|remote| Url::parse(&remote).ok())) 1330 Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1316} 1331}
1317 1332
1333pub(crate) fn handle_open_cargo_toml(
1334 snap: GlobalStateSnapshot,
1335 params: lsp_ext::OpenCargoTomlParams,
1336) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
1337 let _p = profile::span("handle_open_cargo_toml");
1338 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1339 let maybe_cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
1340 if maybe_cargo_spec.is_none() {
1341 return Ok(None);
1342 }
1343
1344 let cargo_spec = maybe_cargo_spec.unwrap();
1345 let cargo_toml_path = cargo_spec.workspace_root.join("Cargo.toml");
1346 if !cargo_toml_path.exists() {
1347 return Ok(None);
1348 }
1349 let cargo_toml_url = to_proto::url_from_abs_path(&cargo_toml_path);
1350 let cargo_toml_location = Location::new(cargo_toml_url, Range::default());
1351 let res = lsp_types::GotoDefinitionResponse::from(cargo_toml_location);
1352 Ok(Some(res))
1353}
1354
1318fn implementation_title(count: usize) -> String { 1355fn implementation_title(count: usize) -> String {
1319 if count == 1 { 1356 if count == 1 {
1320 "1 implementation".into() 1357 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index f31f8d900..93ac45415 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -113,22 +113,6 @@ pub struct JoinLinesParams {
113 pub ranges: Vec<Range>, 113 pub ranges: Vec<Range>,
114} 114}
115 115
116pub enum ResolveCodeActionRequest {}
117
118impl Request for ResolveCodeActionRequest {
119 type Params = ResolveCodeActionParams;
120 type Result = Option<SnippetWorkspaceEdit>;
121 const METHOD: &'static str = "experimental/resolveCodeAction";
122}
123
124/// Params for the ResolveCodeActionRequest
125#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
126#[serde(rename_all = "camelCase")]
127pub struct ResolveCodeActionParams {
128 pub code_action_params: lsp_types::CodeActionParams,
129 pub id: String,
130}
131
132pub enum OnEnter {} 116pub enum OnEnter {}
133 117
134impl Request for OnEnter { 118impl Request for OnEnter {
@@ -265,13 +249,18 @@ impl Request for CodeActionRequest {
265 const METHOD: &'static str = "textDocument/codeAction"; 249 const METHOD: &'static str = "textDocument/codeAction";
266} 250}
267 251
252pub enum CodeActionResolveRequest {}
253impl Request for CodeActionResolveRequest {
254 type Params = CodeAction;
255 type Result = CodeAction;
256 const METHOD: &'static str = "codeAction/resolve";
257}
258
268#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] 259#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
269#[serde(rename_all = "camelCase")] 260#[serde(rename_all = "camelCase")]
270pub struct CodeAction { 261pub struct CodeAction {
271 pub title: String, 262 pub title: String,
272 #[serde(skip_serializing_if = "Option::is_none")] 263 #[serde(skip_serializing_if = "Option::is_none")]
273 pub id: Option<String>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub group: Option<String>, 264 pub group: Option<String>,
276 #[serde(skip_serializing_if = "Option::is_none")] 265 #[serde(skip_serializing_if = "Option::is_none")]
277 pub kind: Option<CodeActionKind>, 266 pub kind: Option<CodeActionKind>,
@@ -282,6 +271,16 @@ pub struct CodeAction {
282 pub edit: Option<SnippetWorkspaceEdit>, 271 pub edit: Option<SnippetWorkspaceEdit>,
283 #[serde(skip_serializing_if = "Option::is_none")] 272 #[serde(skip_serializing_if = "Option::is_none")]
284 pub is_preferred: Option<bool>, 273 pub is_preferred: Option<bool>,
274
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub data: Option<CodeActionData>,
277}
278
279#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct CodeActionData {
282 pub code_action_params: lsp_types::CodeActionParams,
283 pub id: String,
285} 284}
286 285
287#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 286#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
@@ -303,7 +302,7 @@ pub enum SnippetDocumentChangeOperation {
303#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] 302#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
304#[serde(rename_all = "camelCase")] 303#[serde(rename_all = "camelCase")]
305pub struct SnippetTextDocumentEdit { 304pub struct SnippetTextDocumentEdit {
306 pub text_document: lsp_types::VersionedTextDocumentIdentifier, 305 pub text_document: lsp_types::OptionalVersionedTextDocumentIdentifier,
307 pub edits: Vec<SnippetTextEdit>, 306 pub edits: Vec<SnippetTextEdit>,
308} 307}
309 308
@@ -355,3 +354,17 @@ impl Request for ExternalDocs {
355 type Result = Option<lsp_types::Url>; 354 type Result = Option<lsp_types::Url>;
356 const METHOD: &'static str = "experimental/externalDocs"; 355 const METHOD: &'static str = "experimental/externalDocs";
357} 356}
357
358pub enum OpenCargoToml {}
359
360impl Request for OpenCargoToml {
361 type Params = OpenCargoTomlParams;
362 type Result = Option<lsp_types::GotoDefinitionResponse>;
363 const METHOD: &'static str = "experimental/openCargoToml";
364}
365
366#[derive(Serialize, Deserialize, Debug)]
367#[serde(rename_all = "camelCase")]
368pub struct OpenCargoTomlParams {
369 pub text_document: TextDocumentIdentifier,
370}
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 1d271a9d8..6427c7367 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -51,7 +51,7 @@ impl GlobalState {
51 } 51 }
52 let percentage = fraction.map(|f| { 52 let percentage = fraction.map(|f| {
53 assert!(0.0 <= f && f <= 1.0); 53 assert!(0.0 <= f && f <= 1.0);
54 f * 100.0 54 (f * 100.0) as u32
55 }); 55 });
56 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title)); 56 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
57 let work_done_progress = match state { 57 let work_done_progress = match state {
@@ -98,11 +98,11 @@ pub(crate) fn apply_document_changes(
98 // The VFS will normalize the end of lines to `\n`. 98 // The VFS will normalize the end of lines to `\n`.
99 enum IndexValid { 99 enum IndexValid {
100 All, 100 All,
101 UpToLineExclusive(u64), 101 UpToLineExclusive(u32),
102 } 102 }
103 103
104 impl IndexValid { 104 impl IndexValid {
105 fn covers(&self, line: u64) -> bool { 105 fn covers(&self, line: u32) -> bool {
106 match *self { 106 match *self {
107 IndexValid::UpToLineExclusive(to) => to > line, 107 IndexValid::UpToLineExclusive(to) => to > line,
108 _ => true, 108 _ => true,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index d504572a8..b34ff092d 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -368,7 +368,7 @@ impl GlobalState {
368 let url = file_id_to_url(&self.vfs.read().0, file_id); 368 let url = file_id_to_url(&self.vfs.read().0, file_id);
369 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect(); 369 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect();
370 let version = from_proto::vfs_path(&url) 370 let version = from_proto::vfs_path(&url)
371 .map(|path| self.mem_docs.get(&path)?.version) 371 .map(|path| self.mem_docs.get(&path).map(|it| it.version))
372 .unwrap_or_default(); 372 .unwrap_or_default();
373 373
374 self.send_notification::<lsp_types::notification::PublishDiagnostics>( 374 self.send_notification::<lsp_types::notification::PublishDiagnostics>(
@@ -435,9 +435,10 @@ impl GlobalState {
435 .on::<lsp_ext::Runnables>(handlers::handle_runnables) 435 .on::<lsp_ext::Runnables>(handlers::handle_runnables)
436 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints) 436 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)
437 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action) 437 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)
438 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action) 438 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover) 439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) 440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
441 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
441 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) 442 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
442 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) 443 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
443 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) 444 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
@@ -502,7 +503,13 @@ impl GlobalState {
502 })? 503 })?
503 .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| { 504 .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
504 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { 505 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
505 let doc = this.mem_docs.get_mut(&path).unwrap(); 506 let doc = match this.mem_docs.get_mut(&path) {
507 Some(doc) => doc,
508 None => {
509 log::error!("expected DidChangeTextDocument: {}", path);
510 return Ok(());
511 }
512 };
506 let vfs = &mut this.vfs.write().0; 513 let vfs = &mut this.vfs.write().0;
507 let file_id = vfs.file_id(&path).unwrap(); 514 let file_id = vfs.file_id(&path).unwrap();
508 let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); 515 let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
@@ -520,7 +527,7 @@ impl GlobalState {
520 let mut version = None; 527 let mut version = None;
521 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { 528 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
522 match this.mem_docs.remove(&path) { 529 match this.mem_docs.remove(&path) {
523 Some(doc) => version = doc.version, 530 Some(doc) => version = Some(doc.version),
524 None => log::error!("orphan DidCloseTextDocument: {}", path), 531 None => log::error!("orphan DidCloseTextDocument: {}", path),
525 } 532 }
526 533
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 0eabd51bd..001bf5949 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -96,17 +96,12 @@ impl GlobalState {
96 self.task_pool.handle.spawn({ 96 self.task_pool.handle.spawn({
97 let linked_projects = self.config.linked_projects.clone(); 97 let linked_projects = self.config.linked_projects.clone();
98 let cargo_config = self.config.cargo.clone(); 98 let cargo_config = self.config.cargo.clone();
99 let with_sysroot = self.config.with_sysroot.clone();
100 move || { 99 move || {
101 let workspaces = linked_projects 100 let workspaces = linked_projects
102 .iter() 101 .iter()
103 .map(|project| match project { 102 .map(|project| match project {
104 LinkedProject::ProjectManifest(manifest) => { 103 LinkedProject::ProjectManifest(manifest) => {
105 project_model::ProjectWorkspace::load( 104 project_model::ProjectWorkspace::load(manifest.clone(), &cargo_config)
106 manifest.clone(),
107 &cargo_config,
108 with_sysroot,
109 )
110 } 105 }
111 LinkedProject::InlineJsonProject(it) => { 106 LinkedProject::InlineJsonProject(it) => {
112 project_model::ProjectWorkspace::load_inline(it.clone()) 107 project_model::ProjectWorkspace::load_inline(it.clone())
@@ -208,7 +203,11 @@ impl GlobalState {
208 let contents = loader.handle.load_sync(path); 203 let contents = loader.handle.load_sync(path);
209 vfs.set_file_contents(vfs_path.clone(), contents); 204 vfs.set_file_contents(vfs_path.clone(), contents);
210 } 205 }
211 vfs.file_id(&vfs_path) 206 let res = vfs.file_id(&vfs_path);
207 if res.is_none() {
208 log::error!("failed to load {}", path.display())
209 }
210 res
212 }; 211 };
213 for ws in workspaces.iter() { 212 for ws in workspaces.iter() {
214 crate_graph.extend(ws.to_crate_graph( 213 crate_graph.extend(ws.to_crate_graph(
@@ -246,7 +245,9 @@ impl GlobalState {
246 .iter() 245 .iter()
247 .enumerate() 246 .enumerate()
248 .filter_map(|(id, w)| match w { 247 .filter_map(|(id, w)| match w {
249 ProjectWorkspace::Cargo { cargo, sysroot: _ } => Some((id, cargo.workspace_root())), 248 ProjectWorkspace::Cargo { cargo, sysroot: _, rustc: _ } => {
249 Some((id, cargo.workspace_root()))
250 }
250 ProjectWorkspace::Json { project, .. } => { 251 ProjectWorkspace::Json { project, .. } => {
251 // Enable flychecks for json projects if a custom flycheck command was supplied 252 // Enable flychecks for json projects if a custom flycheck command was supplied
252 // in the workspace configuration. 253 // in the workspace configuration.
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 92b7c7b68..2052b800c 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -21,9 +21,7 @@ use crate::{
21 21
22pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { 22pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
23 let line_col = line_index.line_col(offset); 23 let line_col = line_index.line_col(offset);
24 let line = u64::from(line_col.line); 24 lsp_types::Position::new(line_col.line, line_col.col_utf16)
25 let character = u64::from(line_col.col_utf16);
26 lsp_types::Position::new(line, character)
27} 25}
28 26
29pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range { 27pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
@@ -278,9 +276,9 @@ pub(crate) fn signature_help(
278 label.push_str(", "); 276 label.push_str(", ");
279 } 277 }
280 first = false; 278 first = false;
281 let start = label.len() as u64; 279 let start = label.len() as u32;
282 label.push_str(param); 280 label.push_str(param);
283 let end = label.len() as u64; 281 let end = label.len() as u32;
284 params.push(lsp_types::ParameterInformation { 282 params.push(lsp_types::ParameterInformation {
285 label: lsp_types::ParameterLabel::LabelOffsets([start, end]), 283 label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
286 documentation: None, 284 documentation: None,
@@ -302,7 +300,7 @@ pub(crate) fn signature_help(
302 }) 300 })
303 }; 301 };
304 302
305 let active_parameter = call_info.active_parameter.map(|it| it as i64); 303 let active_parameter = call_info.active_parameter.map(|it| it as u32);
306 304
307 let signature = lsp_types::SignatureInformation { 305 let signature = lsp_types::SignatureInformation {
308 label, 306 label,
@@ -426,6 +424,7 @@ fn semantic_token_type_and_modifiers(
426 HighlightModifier::Consuming => semantic_tokens::CONSUMING, 424 HighlightModifier::Consuming => semantic_tokens::CONSUMING,
427 HighlightModifier::Unsafe => semantic_tokens::UNSAFE, 425 HighlightModifier::Unsafe => semantic_tokens::UNSAFE,
428 HighlightModifier::Callable => semantic_tokens::CALLABLE, 426 HighlightModifier::Callable => semantic_tokens::CALLABLE,
427 HighlightModifier::Static => lsp_types::SemanticTokenModifier::STATIC,
429 }; 428 };
430 mods |= modifier; 429 mods |= modifier;
431 } 430 }
@@ -517,13 +516,13 @@ pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url {
517 lsp_types::Url::parse(&url).unwrap() 516 lsp_types::Url::parse(&url).unwrap()
518} 517}
519 518
520pub(crate) fn versioned_text_document_identifier( 519pub(crate) fn optional_versioned_text_document_identifier(
521 snap: &GlobalStateSnapshot, 520 snap: &GlobalStateSnapshot,
522 file_id: FileId, 521 file_id: FileId,
523) -> lsp_types::VersionedTextDocumentIdentifier { 522) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
524 let url = url(snap, file_id); 523 let url = url(snap, file_id);
525 let version = snap.url_file_version(&url); 524 let version = snap.url_file_version(&url);
526 lsp_types::VersionedTextDocumentIdentifier { uri: url, version } 525 lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
527} 526}
528 527
529pub(crate) fn location( 528pub(crate) fn location(
@@ -612,7 +611,7 @@ pub(crate) fn snippet_text_document_edit(
612 is_snippet: bool, 611 is_snippet: bool,
613 source_file_edit: SourceFileEdit, 612 source_file_edit: SourceFileEdit,
614) -> Result<lsp_ext::SnippetTextDocumentEdit> { 613) -> Result<lsp_ext::SnippetTextDocumentEdit> {
615 let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id); 614 let text_document = optional_versioned_text_document_identifier(snap, source_file_edit.file_id);
616 let line_index = snap.analysis.file_line_index(source_file_edit.file_id)?; 615 let line_index = snap.analysis.file_line_index(source_file_edit.file_id)?;
617 let line_endings = snap.file_line_endings(source_file_edit.file_id); 616 let line_endings = snap.file_line_endings(source_file_edit.file_id);
618 let edits = source_file_edit 617 let edits = source_file_edit
@@ -630,12 +629,21 @@ pub(crate) fn resource_op(
630 match file_system_edit { 629 match file_system_edit {
631 FileSystemEdit::CreateFile { anchor, dst } => { 630 FileSystemEdit::CreateFile { anchor, dst } => {
632 let uri = snap.anchored_path(anchor, &dst); 631 let uri = snap.anchored_path(anchor, &dst);
633 lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) 632 lsp_types::ResourceOp::Create(lsp_types::CreateFile {
633 uri,
634 options: None,
635 annotation: None,
636 })
634 } 637 }
635 FileSystemEdit::MoveFile { src, anchor, dst } => { 638 FileSystemEdit::MoveFile { src, anchor, dst } => {
636 let old_uri = snap.file_id_to_url(src); 639 let old_uri = snap.file_id_to_url(src);
637 let new_uri = snap.anchored_path(anchor, &dst); 640 let new_uri = snap.anchored_path(anchor, &dst);
638 lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) 641 lsp_types::ResourceOp::Rename(lsp_types::RenameFile {
642 old_uri,
643 new_uri,
644 options: None,
645 annotation: None,
646 })
639 } 647 }
640 } 648 }
641} 649}
@@ -685,9 +693,11 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
685 edits: edit 693 edits: edit
686 .edits 694 .edits
687 .into_iter() 695 .into_iter()
688 .map(|edit| lsp_types::TextEdit { 696 .map(|edit| {
689 range: edit.range, 697 lsp_types::OneOf::Left(lsp_types::TextEdit {
690 new_text: edit.new_text, 698 range: edit.range,
699 new_text: edit.new_text,
700 })
691 }) 701 })
692 .collect(), 702 .collect(),
693 }, 703 },
@@ -734,16 +744,20 @@ pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
734 744
735pub(crate) fn unresolved_code_action( 745pub(crate) fn unresolved_code_action(
736 snap: &GlobalStateSnapshot, 746 snap: &GlobalStateSnapshot,
747 code_action_params: lsp_types::CodeActionParams,
737 assist: Assist, 748 assist: Assist,
738 index: usize, 749 index: usize,
739) -> Result<lsp_ext::CodeAction> { 750) -> Result<lsp_ext::CodeAction> {
740 let res = lsp_ext::CodeAction { 751 let res = lsp_ext::CodeAction {
741 title: assist.label.to_string(), 752 title: assist.label.to_string(),
742 id: Some(format!("{}:{}", assist.id.0, index.to_string())),
743 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0), 753 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
744 kind: Some(code_action_kind(assist.id.1)), 754 kind: Some(code_action_kind(assist.id.1)),
745 edit: None, 755 edit: None,
746 is_preferred: None, 756 is_preferred: None,
757 data: Some(lsp_ext::CodeActionData {
758 id: format!("{}:{}", assist.id.0, index.to_string()),
759 code_action_params,
760 }),
747 }; 761 };
748 Ok(res) 762 Ok(res)
749} 763}
@@ -753,13 +767,19 @@ pub(crate) fn resolved_code_action(
753 assist: ResolvedAssist, 767 assist: ResolvedAssist,
754) -> Result<lsp_ext::CodeAction> { 768) -> Result<lsp_ext::CodeAction> {
755 let change = assist.source_change; 769 let change = assist.source_change;
756 unresolved_code_action(snap, assist.assist, 0).and_then(|it| { 770 let res = lsp_ext::CodeAction {
757 Ok(lsp_ext::CodeAction { 771 edit: Some(snippet_workspace_edit(snap, change)?),
758 id: None, 772 title: assist.assist.label.to_string(),
759 edit: Some(snippet_workspace_edit(snap, change)?), 773 group: assist
760 ..it 774 .assist
761 }) 775 .group
762 }) 776 .filter(|_| snap.config.client_caps.code_action_group)
777 .map(|gr| gr.0),
778 kind: Some(code_action_kind(assist.assist.id.1)),
779 is_preferred: None,
780 data: None,
781 };
782 Ok(res)
763} 783}
764 784
765pub(crate) fn runnable( 785pub(crate) fn runnable(
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index fe9362bc0..456125789 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
@@ -12,7 +12,7 @@ use lsp_types::{
12 notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress, 12 notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress,
13}; 13};
14use lsp_types::{ProgressParams, ProgressParamsValue}; 14use lsp_types::{ProgressParams, ProgressParamsValue};
15use project_model::ProjectManifest; 15use project_model::{CargoConfig, ProjectManifest};
16use rust_analyzer::{ 16use rust_analyzer::{
17 config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject}, 17 config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject},
18 main_loop, 18 main_loop,
@@ -47,8 +47,8 @@ impl<'a> Project<'a> {
47 self 47 self
48 } 48 }
49 49
50 pub(crate) fn with_sysroot(mut self, sysroot: bool) -> Project<'a> { 50 pub(crate) fn with_sysroot(mut self, yes: bool) -> Project<'a> {
51 self.with_sysroot = sysroot; 51 self.with_sysroot = yes;
52 self 52 self
53 } 53 }
54 54
@@ -90,7 +90,7 @@ impl<'a> Project<'a> {
90 work_done_progress: true, 90 work_done_progress: true,
91 ..Default::default() 91 ..Default::default()
92 }, 92 },
93 with_sysroot: self.with_sysroot, 93 cargo: CargoConfig { no_sysroot: !self.with_sysroot, ..Default::default() },
94 linked_projects, 94 linked_projects,
95 files: FilesConfig { watcher: FilesWatcher::Client, exclude: Vec::new() }, 95 files: FilesConfig { watcher: FilesWatcher::Client, exclude: Vec::new() },
96 ..Config::new(tmp_dir_path) 96 ..Config::new(tmp_dir_path)
@@ -108,7 +108,7 @@ pub(crate) fn project(fixture: &str) -> Server {
108} 108}
109 109
110pub(crate) struct Server { 110pub(crate) struct Server {
111 req_id: Cell<u64>, 111 req_id: Cell<i32>,
112 messages: RefCell<Vec<Message>>, 112 messages: RefCell<Vec<Message>>,
113 _thread: jod_thread::JoinHandle<()>, 113 _thread: jod_thread::JoinHandle<()>,
114 client: Connection, 114 client: Connection,
@@ -165,7 +165,7 @@ impl Server {
165 R::Params: Serialize, 165 R::Params: Serialize,
166 { 166 {
167 let id = self.req_id.get(); 167 let id = self.req_id.get();
168 self.req_id.set(id + 1); 168 self.req_id.set(id.wrapping_add(1));
169 169
170 let r = Request::new(id.into(), R::METHOD.to_string(), params); 170 let r = Request::new(id.into(), R::METHOD.to_string(), params);
171 self.send_request_(r) 171 self.send_request_(r)
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 59d89f47d..374ed5910 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -1,8 +1,5 @@
1//! Missing batteries for standard libraries. 1//! Missing batteries for standard libraries.
2use std::{ 2use std::time::Instant;
3 sync::atomic::{AtomicUsize, Ordering},
4 time::Instant,
5};
6 3
7mod macros; 4mod macros;
8pub mod panic_context; 5pub mod panic_context;
@@ -150,31 +147,6 @@ where
150 left 147 left
151} 148}
152 149
153pub struct RacyFlag(AtomicUsize);
154
155impl RacyFlag {
156 pub const fn new() -> RacyFlag {
157 RacyFlag(AtomicUsize::new(!0))
158 }
159
160 pub fn get(&self, init: impl FnMut() -> bool) -> bool {
161 let mut init = Some(init);
162 self.get_impl(&mut || init.take().map_or(false, |mut f| f()))
163 }
164
165 fn get_impl(&self, init: &mut dyn FnMut() -> bool) -> bool {
166 match self.0.load(Ordering::Relaxed) {
167 0 => false,
168 1 => true,
169 _ => {
170 let res = init();
171 self.0.store(if res { 1 } else { 0 }, Ordering::Relaxed);
172 res
173 }
174 }
175 }
176}
177
178#[cfg(test)] 150#[cfg(test)]
179mod tests { 151mod tests {
180 use super::*; 152 use super::*;
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 61d2acb49..1fe907753 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
13[dependencies] 13[dependencies]
14itertools = "0.9.0" 14itertools = "0.9.0"
15rowan = "0.10.0" 15rowan = "0.10.0"
16rustc_lexer = { version = "686.0.0", package = "rustc-ap-rustc_lexer" } 16rustc_lexer = { version = "688.0.0", package = "rustc-ap-rustc_lexer" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_cell = "1.3.1"
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index 9dc7182bd..320c430c9 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -111,18 +111,28 @@ pub enum InsertPosition<T> {
111 111
112type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>; 112type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
113 113
114#[derive(Debug, Hash, PartialEq, Eq)]
115enum TreeDiffInsertPos {
116 After(SyntaxElement),
117 AsFirstChild(SyntaxElement),
118}
119
114#[derive(Debug)] 120#[derive(Debug)]
115pub struct TreeDiff { 121pub struct TreeDiff {
116 replacements: FxHashMap<SyntaxElement, SyntaxElement>, 122 replacements: FxHashMap<SyntaxElement, SyntaxElement>,
117 deletions: Vec<SyntaxElement>, 123 deletions: Vec<SyntaxElement>,
118 // the vec as well as the indexmap are both here to preserve order 124 // the vec as well as the indexmap are both here to preserve order
119 insertions: FxIndexMap<SyntaxElement, Vec<SyntaxElement>>, 125 insertions: FxIndexMap<TreeDiffInsertPos, Vec<SyntaxElement>>,
120} 126}
121 127
122impl TreeDiff { 128impl TreeDiff {
123 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { 129 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
124 for (anchor, to) in self.insertions.iter() { 130 for (anchor, to) in self.insertions.iter() {
125 to.iter().for_each(|to| builder.insert(anchor.text_range().end(), to.to_string())); 131 let offset = match anchor {
132 TreeDiffInsertPos::After(it) => it.text_range().end(),
133 TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(),
134 };
135 to.iter().for_each(|to| builder.insert(offset, to.to_string()));
126 } 136 }
127 for (from, to) in self.replacements.iter() { 137 for (from, to) in self.replacements.iter() {
128 builder.replace(from.text_range(), to.to_string()) 138 builder.replace(from.text_range(), to.to_string())
@@ -188,19 +198,20 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
188 let lhs_child = lhs_children.next(); 198 let lhs_child = lhs_children.next();
189 match (lhs_child.clone(), rhs_children.next()) { 199 match (lhs_child.clone(), rhs_children.next()) {
190 (None, None) => break, 200 (None, None) => break,
191 (None, Some(element)) => match last_lhs.clone() { 201 (None, Some(element)) => {
192 Some(prev) => { 202 let insert_pos = match last_lhs.clone() {
193 mark::hit!(diff_insert); 203 Some(prev) => {
194 diff.insertions.entry(prev).or_insert_with(Vec::new).push(element); 204 mark::hit!(diff_insert);
195 } 205 TreeDiffInsertPos::After(prev)
196 // first iteration, this means we got no anchor element to insert after 206 }
197 // therefor replace the parent node instead 207 // first iteration, insert into out parent as the first child
198 None => { 208 None => {
199 mark::hit!(diff_replace_parent); 209 mark::hit!(diff_insert_as_first_child);
200 diff.replacements.insert(lhs.clone().into(), rhs.clone().into()); 210 TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
201 break; 211 }
202 } 212 };
203 }, 213 diff.insertions.entry(insert_pos).or_insert_with(Vec::new).push(element);
214 }
204 (Some(element), None) => { 215 (Some(element), None) => {
205 mark::hit!(diff_delete); 216 mark::hit!(diff_delete);
206 diff.deletions.push(element); 217 diff.deletions.push(element);
@@ -224,8 +235,15 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
224 } 235 }
225 } 236 }
226 let drain = look_ahead_scratch.drain(..); 237 let drain = look_ahead_scratch.drain(..);
227 if let Some(prev) = last_lhs.clone().filter(|_| insert) { 238 if insert {
228 diff.insertions.entry(prev).or_insert_with(Vec::new).extend(drain); 239 let insert_pos = if let Some(prev) = last_lhs.clone().filter(|_| insert) {
240 TreeDiffInsertPos::After(prev)
241 } else {
242 mark::hit!(insert_first_child);
243 TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
244 };
245
246 diff.insertions.entry(insert_pos).or_insert_with(Vec::new).extend(drain);
229 rhs_children = rhs_children_clone; 247 rhs_children = rhs_children_clone;
230 } else { 248 } else {
231 go(diff, lhs_ele, rhs_ele) 249 go(diff, lhs_ele, rhs_ele)
@@ -331,7 +349,10 @@ pub struct SyntaxRewriter<'a> {
331 349
332impl fmt::Debug for SyntaxRewriter<'_> { 350impl fmt::Debug for SyntaxRewriter<'_> {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 f.debug_struct("SyntaxRewriter").field("replacements", &self.replacements).finish() 352 f.debug_struct("SyntaxRewriter")
353 .field("replacements", &self.replacements)
354 .field("insertions", &self.insertions)
355 .finish()
335 } 356 }
336} 357}
337 358
@@ -629,18 +650,19 @@ mod tests {
629 650
630 #[test] 651 #[test]
631 fn replace_parent() { 652 fn replace_parent() {
632 mark::check!(diff_replace_parent); 653 mark::check!(diff_insert_as_first_child);
633 check_diff( 654 check_diff(
634 r#""#, 655 r#""#,
635 r#"use foo::bar;"#, 656 r#"use foo::bar;"#,
636 expect![[r#" 657 expect![[r#"
637 insertions: 658 insertions:
638 659
639 660 Line 0: AsFirstChild(Node([email protected]))
661 -> use foo::bar;
640 662
641 replacements: 663 replacements:
642 664
643 Line 0: Node([email protected]) -> use foo::bar; 665
644 666
645 deletions: 667 deletions:
646 668
@@ -663,7 +685,7 @@ use baz;"#,
663 expect![[r#" 685 expect![[r#"
664 insertions: 686 insertions:
665 687
666 Line 2: Node([email protected]) 688 Line 2: After(Node([email protected]))
667 -> "\n" 689 -> "\n"
668 -> use baz; 690 -> use baz;
669 691
@@ -691,7 +713,7 @@ use baz;"#,
691 expect![[r#" 713 expect![[r#"
692 insertions: 714 insertions:
693 715
694 Line 2: Token([email protected] "\n") 716 Line 2: After(Token([email protected] "\n"))
695 -> use bar; 717 -> use bar;
696 -> "\n" 718 -> "\n"
697 719
@@ -719,7 +741,7 @@ use baz;"#,
719 expect![[r#" 741 expect![[r#"
720 insertions: 742 insertions:
721 743
722 Line 0: Token([email protected] "\n") 744 Line 0: After(Token([email protected] "\n"))
723 -> use foo; 745 -> use foo;
724 -> "\n" 746 -> "\n"
725 747
@@ -735,6 +757,36 @@ use baz;"#,
735 } 757 }
736 758
737 #[test] 759 #[test]
760 fn first_child_insertion() {
761 mark::check!(insert_first_child);
762 check_diff(
763 r#"fn main() {
764 stdi
765 }"#,
766 r#"use foo::bar;
767
768 fn main() {
769 stdi
770 }"#,
771 expect![[r#"
772 insertions:
773
774 Line 0: AsFirstChild(Node([email protected]))
775 -> use foo::bar;
776 -> "\n\n "
777
778 replacements:
779
780
781
782 deletions:
783
784
785 "#]],
786 );
787 }
788
789 #[test]
738 fn delete_last() { 790 fn delete_last() {
739 mark::check!(diff_delete); 791 mark::check!(diff_delete);
740 check_diff( 792 check_diff(
@@ -776,7 +828,7 @@ use crate::AstNode;
776 expect![[r#" 828 expect![[r#"
777 insertions: 829 insertions:
778 830
779 Line 1: Node([email protected]) 831 Line 1: After(Node([email protected]))
780 -> "\n\n" 832 -> "\n\n"
781 -> use crate::AstNode; 833 -> use crate::AstNode;
782 834
@@ -842,10 +894,10 @@ use std::ops::{self, RangeInclusive};
842 expect![[r#" 894 expect![[r#"
843 insertions: 895 insertions:
844 896
845 Line 2: Node([email protected]) 897 Line 2: After(Node([email protected]))
846 -> :: 898 -> ::
847 -> fmt 899 -> fmt
848 Line 6: Token([email protected] "\n") 900 Line 6: After(Token([email protected] "\n"))
849 -> use std::hash::BuildHasherDefault; 901 -> use std::hash::BuildHasherDefault;
850 -> "\n" 902 -> "\n"
851 -> use std::ops::{self, RangeInclusive}; 903 -> use std::ops::{self, RangeInclusive};
@@ -889,14 +941,14 @@ fn main() {
889 expect![[r#" 941 expect![[r#"
890 insertions: 942 insertions:
891 943
892 Line 3: Node([email protected]) 944 Line 3: After(Node([email protected]))
893 -> " " 945 -> " "
894 -> match Err(92) { 946 -> match Err(92) {
895 Ok(it) => it, 947 Ok(it) => it,
896 _ => return, 948 _ => return,
897 } 949 }
898 -> ; 950 -> ;
899 Line 3: Node([email protected]) 951 Line 3: After(Node([email protected]))
900 -> "\n " 952 -> "\n "
901 -> foo(x); 953 -> foo(x);
902 954
@@ -931,14 +983,18 @@ fn main() {
931 _ => format!("{}", syn), 983 _ => format!("{}", syn),
932 }; 984 };
933 985
934 let insertions = diff.insertions.iter().format_with("\n", |(k, v), f| { 986 let insertions =
935 f(&format!( 987 diff.insertions.iter().format_with("\n", |(k, v), f| -> Result<(), std::fmt::Error> {
936 "Line {}: {:?}\n-> {}", 988 f(&format!(
937 line_number(k), 989 "Line {}: {:?}\n-> {}",
938 k, 990 line_number(match k {
939 v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v))) 991 super::TreeDiffInsertPos::After(syn) => syn,
940 )) 992 super::TreeDiffInsertPos::AsFirstChild(syn) => syn,
941 }); 993 }),
994 k,
995 v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v)))
996 ))
997 });
942 998
943 let replacements = diff 999 let replacements = diff
944 .replacements 1000 .replacements
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index 8a0e3d27b..7844f9ed6 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -115,10 +115,10 @@ fn test_doc_comment_none() {
115} 115}
116 116
117#[test] 117#[test]
118fn test_doc_comment_of_items() { 118fn test_outer_doc_comment_of_items() {
119 let file = SourceFile::parse( 119 let file = SourceFile::parse(
120 r#" 120 r#"
121 //! doc 121 /// doc
122 // non-doc 122 // non-doc
123 mod foo {} 123 mod foo {}
124 "#, 124 "#,
@@ -130,6 +130,21 @@ fn test_doc_comment_of_items() {
130} 130}
131 131
132#[test] 132#[test]
133fn test_inner_doc_comment_of_items() {
134 let file = SourceFile::parse(
135 r#"
136 //! doc
137 // non-doc
138 mod foo {}
139 "#,
140 )
141 .ok()
142 .unwrap();
143 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
144 assert!(module.doc_comment_text().is_none());
145}
146
147#[test]
133fn test_doc_comment_of_statics() { 148fn test_doc_comment_of_statics() {
134 let file = SourceFile::parse( 149 let file = SourceFile::parse(
135 r#" 150 r#"
diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs
index 9253c97d0..e4a9b945c 100644
--- a/crates/syntax/src/ast/expr_ext.rs
+++ b/crates/syntax/src/ast/expr_ext.rs
@@ -22,6 +22,18 @@ impl ast::Expr {
22 _ => false, 22 _ => false,
23 } 23 }
24 } 24 }
25
26 pub fn name_ref(&self) -> Option<ast::NameRef> {
27 if let ast::Expr::PathExpr(expr) = self {
28 let path = expr.path()?;
29 let segment = path.segment()?;
30 let name_ref = segment.name_ref()?;
31 if path.qualifier().is_none() {
32 return Some(name_ref);
33 }
34 }
35 None
36 }
25} 37}
26 38
27#[derive(Debug, Clone, PartialEq, Eq)] 39#[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index b1578820f..876659a2b 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -25,6 +25,10 @@ pub fn assoc_item_list() -> ast::AssocItemList {
25 ast_from_text("impl C for D {};") 25 ast_from_text("impl C for D {};")
26} 26}
27 27
28pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {
29 ast_from_text(&format!("impl {} for {} {{}}", trait_, ty))
30}
31
28pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { 32pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
29 ast_from_text(&format!("use {};", name_ref)) 33 ast_from_text(&format!("use {};", name_ref))
30} 34}
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index ce35ac01a..b70b840b8 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -203,15 +203,7 @@ impl ast::RecordExprField {
203 if let Some(name_ref) = self.name_ref() { 203 if let Some(name_ref) = self.name_ref() {
204 return Some(name_ref); 204 return Some(name_ref);
205 } 205 }
206 if let Some(ast::Expr::PathExpr(expr)) = self.expr() { 206 self.expr()?.name_ref()
207 let path = expr.path()?;
208 let segment = path.segment()?;
209 let name_ref = segment.name_ref()?;
210 if path.qualifier().is_none() {
211 return Some(name_ref);
212 }
213 }
214 None
215 } 207 }
216} 208}
217 209
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index e4e512f2e..ac0326420 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -14,16 +14,15 @@ use crate::{
14 14
15impl ast::Comment { 15impl ast::Comment {
16 pub fn kind(&self) -> CommentKind { 16 pub fn kind(&self) -> CommentKind {
17 kind_by_prefix(self.text()) 17 CommentKind::from_text(self.text())
18 } 18 }
19 19
20 pub fn prefix(&self) -> &'static str { 20 pub fn prefix(&self) -> &'static str {
21 for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() { 21 let &(prefix, _kind) = CommentKind::BY_PREFIX
22 if *k == self.kind() && self.text().starts_with(prefix) { 22 .iter()
23 return prefix; 23 .find(|&(prefix, kind)| self.kind() == *kind && self.text().starts_with(prefix))
24 } 24 .unwrap();
25 } 25 prefix
26 unreachable!()
27 } 26 }
28} 27}
29 28
@@ -55,29 +54,25 @@ pub enum CommentPlacement {
55 Outer, 54 Outer,
56} 55}
57 56
58const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = { 57impl CommentKind {
59 use {CommentPlacement::*, CommentShape::*}; 58 const BY_PREFIX: [(&'static str, CommentKind); 8] = [
60 &[ 59 ("/**/", CommentKind { shape: CommentShape::Block, doc: None }),
61 ("////", CommentKind { shape: Line, doc: None }), 60 ("////", CommentKind { shape: CommentShape::Line, doc: None }),
62 ("///", CommentKind { shape: Line, doc: Some(Outer) }), 61 ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }),
63 ("//!", CommentKind { shape: Line, doc: Some(Inner) }), 62 ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }),
64 ("/**", CommentKind { shape: Block, doc: Some(Outer) }), 63 ("/**", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Outer) }),
65 ("/*!", CommentKind { shape: Block, doc: Some(Inner) }), 64 ("/*!", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Inner) }),
66 ("//", CommentKind { shape: Line, doc: None }), 65 ("//", CommentKind { shape: CommentShape::Line, doc: None }),
67 ("/*", CommentKind { shape: Block, doc: None }), 66 ("/*", CommentKind { shape: CommentShape::Block, doc: None }),
68 ] 67 ];
69};
70 68
71fn kind_by_prefix(text: &str) -> CommentKind { 69 pub(crate) fn from_text(text: &str) -> CommentKind {
72 if text == "/**/" { 70 let &(_prefix, kind) = CommentKind::BY_PREFIX
73 return CommentKind { shape: CommentShape::Block, doc: None }; 71 .iter()
74 } 72 .find(|&(prefix, _kind)| text.starts_with(prefix))
75 for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() { 73 .unwrap();
76 if text.starts_with(prefix) { 74 kind
77 return *kind;
78 }
79 } 75 }
80 panic!("bad comment text: {:?}", text)
81} 76}
82 77
83impl ast::Whitespace { 78impl ast::Whitespace {
@@ -336,10 +331,22 @@ pub trait HasFormatSpecifier: AstToken {
336 } 331 }
337 c if c == '_' || c.is_alphabetic() => { 332 c if c == '_' || c.is_alphabetic() => {
338 read_identifier(&mut chars, &mut callback); 333 read_identifier(&mut chars, &mut callback);
334
335 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
336 == Some('?')
337 {
338 skip_char_and_emit(
339 &mut chars,
340 FormatSpecifier::QuestionMark,
341 &mut callback,
342 );
343 }
344
339 // can be either width (indicated by dollar sign, or type in which case 345 // can be either width (indicated by dollar sign, or type in which case
340 // the next sign has to be `}`) 346 // the next sign has to be `}`)
341 let next = 347 let next =
342 chars.peek().and_then(|next| next.1.as_ref().ok()).copied(); 348 chars.peek().and_then(|next| next.1.as_ref().ok()).copied();
349
343 match next { 350 match next {
344 Some('$') => skip_char_and_emit( 351 Some('$') => skip_char_and_emit(
345 &mut chars, 352 &mut chars,
@@ -422,6 +429,16 @@ pub trait HasFormatSpecifier: AstToken {
422 } 429 }
423 c if c == '_' || c.is_alphabetic() => { 430 c if c == '_' || c.is_alphabetic() => {
424 read_identifier(&mut chars, &mut callback); 431 read_identifier(&mut chars, &mut callback);
432
433 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
434 == Some('?')
435 {
436 skip_char_and_emit(
437 &mut chars,
438 FormatSpecifier::QuestionMark,
439 &mut callback,
440 );
441 }
425 } 442 }
426 _ => {} 443 _ => {}
427 } 444 }
diff --git a/crates/syntax/src/parsing/text_tree_sink.rs b/crates/syntax/src/parsing/text_tree_sink.rs
index c1b5f246d..997bc5d28 100644
--- a/crates/syntax/src/parsing/text_tree_sink.rs
+++ b/crates/syntax/src/parsing/text_tree_sink.rs
@@ -5,6 +5,7 @@ use std::mem;
5use parser::{ParseError, TreeSink}; 5use parser::{ParseError, TreeSink};
6 6
7use crate::{ 7use crate::{
8 ast,
8 parsing::Token, 9 parsing::Token,
9 syntax_node::GreenNode, 10 syntax_node::GreenNode,
10 SmolStr, SyntaxError, 11 SmolStr, SyntaxError,
@@ -153,24 +154,22 @@ fn n_attached_trivias<'a>(
153 154
154 while let Some((i, (kind, text))) = trivias.next() { 155 while let Some((i, (kind, text))) = trivias.next() {
155 match kind { 156 match kind {
156 WHITESPACE => { 157 WHITESPACE if text.contains("\n\n") => {
157 if text.contains("\n\n") { 158 // we check whether the next token is a doc-comment
158 // we check whether the next token is a doc-comment 159 // and skip the whitespace in this case
159 // and skip the whitespace in this case 160 if let Some((COMMENT, peek_text)) = trivias.peek().map(|(_, pair)| pair) {
160 if let Some((peek_kind, peek_text)) = 161 let comment_kind = ast::CommentKind::from_text(peek_text);
161 trivias.peek().map(|(_, pair)| pair) 162 if comment_kind.doc == Some(ast::CommentPlacement::Outer) {
162 { 163 continue;
163 if *peek_kind == COMMENT
164 && peek_text.starts_with("///")
165 && !peek_text.starts_with("////")
166 {
167 continue;
168 }
169 } 164 }
170 break;
171 } 165 }
166 break;
172 } 167 }
173 COMMENT => { 168 COMMENT => {
169 let comment_kind = ast::CommentKind::from_text(text);
170 if comment_kind.doc == Some(ast::CommentPlacement::Inner) {
171 break;
172 }
174 res = i + 1; 173 res = i + 1;
175 } 174 }
176 _ => (), 175 _ => (),
diff --git a/crates/syntax/test_data/parser/ok/0037_mod.rast b/crates/syntax/test_data/parser/ok/0037_mod.rast
index 1d5d94bde..35577272e 100644
--- a/crates/syntax/test_data/parser/ok/0037_mod.rast
+++ b/crates/syntax/test_data/parser/ok/0037_mod.rast
@@ -1,9 +1,9 @@
1[email protected] 1[email protected]
2 [email protected] "// https://github.com ..." 2 [email protected] "// https://github.com ..."
3 [email protected] "\n\n" 3 [email protected] "\n\n"
4 MODULE@62..93 4 COMMENT@62..70 "//! docs"
5 COMMENT@62..70 "//! docs" 5 WHITESPACE@70..71 "\n"
6 WHITESPACE@70..71 "\n" 6 MODULE@71..93
7 [email protected] "// non-docs" 7 [email protected] "// non-docs"
8 [email protected] "\n" 8 [email protected] "\n"
9 [email protected] "mod" 9 [email protected] "mod"
diff --git a/crates/syntax/test_data/parser/ok/0068_item_modifiers.rast b/crates/syntax/test_data/parser/ok/0068_item_modifiers.rast
index 50a6d8ee9..87eebf185 100644
--- a/crates/syntax/test_data/parser/ok/0068_item_modifiers.rast
+++ b/crates/syntax/test_data/parser/ok/0068_item_modifiers.rast
@@ -1,4 +1,4 @@
1[email protected]04 1[email protected]28
2 [email protected] 2 [email protected]
3 [email protected] "async" 3 [email protected] "async"
4 [email protected] " " 4 [email protected] " "
@@ -215,4 +215,16 @@ [email protected]
215 [email protected] 215 [email protected]
216 [email protected] "{" 216 [email protected] "{"
217 [email protected] "}" 217 [email protected] "}"
218 [email protected] "\n" 218 [email protected] "\n\n"
219 [email protected]
220 [email protected] "unsafe"
221 [email protected] " "
222 [email protected]
223 [email protected] "extern"
224 [email protected] " "
225 [email protected] "\"C++\""
226 [email protected] " "
227 [email protected]
228 [email protected] "{"
229 [email protected] "}"
230 [email protected] "\n"
diff --git a/crates/syntax/test_data/parser/ok/0068_item_modifiers.rs b/crates/syntax/test_data/parser/ok/0068_item_modifiers.rs
index 8d697c04b..6d27a082c 100644
--- a/crates/syntax/test_data/parser/ok/0068_item_modifiers.rs
+++ b/crates/syntax/test_data/parser/ok/0068_item_modifiers.rs
@@ -14,3 +14,5 @@ unsafe auto trait T {}
14unsafe impl Foo {} 14unsafe impl Foo {}
15default impl Foo {} 15default impl Foo {}
16unsafe default impl Foo {} 16unsafe default impl Foo {}
17
18unsafe extern "C++" {}
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 780f5cb91..8c01db07c 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,8 +1,8 @@
1<!--- 1<!---
2lsp_ext.rs hash: 286f8bbac885531a 2lsp_ext.rs hash: 203fdf79b21b5987
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
6 6
7 https://github.com/rust-analyzer/rust-analyzer/issues/4604 7 https://github.com/rust-analyzer/rust-analyzer/issues/4604
8 8
@@ -45,7 +45,7 @@ interface SnippetTextEdit extends TextEdit {
45 45
46```typescript 46```typescript
47export interface TextDocumentEdit { 47export interface TextDocumentEdit {
48 textDocument: VersionedTextDocumentIdentifier; 48 textDocument: OptionalVersionedTextDocumentIdentifier;
49 edits: (TextEdit | SnippetTextEdit)[]; 49 edits: (TextEdit | SnippetTextEdit)[];
50} 50}
51``` 51```
@@ -109,30 +109,6 @@ Invoking code action at this position will yield two code actions for importing
109* Is a fixed two-level structure enough? 109* Is a fixed two-level structure enough?
110* Should we devise a general way to encode custom interaction protocols for GUI refactorings? 110* Should we devise a general way to encode custom interaction protocols for GUI refactorings?
111 111
112## Lazy assists with `ResolveCodeAction`
113
114**Issue:** https://github.com/microsoft/language-server-protocol/issues/787
115
116**Client Capability** `{ "resolveCodeAction": boolean }`
117
118If this capability is set, the assists will be computed lazily. Thus `CodeAction` returned from the server will only contain `id` but not `edit` or `command` fields. The only exclusion from the rule is the diagnostic edits.
119
120After the client got the id, it should then call `experimental/resolveCodeAction` command on the server and provide the following payload:
121
122```typescript
123interface ResolveCodeActionParams {
124 id: string;
125 codeActionParams: lc.CodeActionParams;
126}
127```
128
129As a result of the command call the client will get the respective workspace edit (`lc.WorkspaceEdit`).
130
131### Unresolved Questions
132
133* Apply smarter filtering for ids?
134* Upon `resolveCodeAction` command only call the assits which should be resolved and not all of them?
135
136## Parent Module 112## Parent Module
137 113
138**Issue:** https://github.com/microsoft/language-server-protocol/issues/1002 114**Issue:** https://github.com/microsoft/language-server-protocol/issues/1002
@@ -561,3 +537,28 @@ Such actions on the client side are appended to a hover bottom as command links:
561 +-----------------------------+ 537 +-----------------------------+
562 ... 538 ...
563``` 539```
540
541## Open Cargo.toml
542
543**Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6462
544
545This request is sent from client to server to open the current project's Cargo.toml
546
547**Method:** `experimental/openCargoToml`
548
549**Request:** `OpenCargoTomlParams`
550
551**Response:** `Location | null`
552
553
554### Example
555
556```rust
557// Cargo.toml
558[package]
559// src/main.rs
560
561/* cursor here*/
562```
563
564`experimental/openCargoToml` returns a single `Link` to the start of the `[package]` keyword.
diff --git a/docs/dev/syntax.md b/docs/dev/syntax.md
index 2eb08b7ca..1edafab68 100644
--- a/docs/dev/syntax.md
+++ b/docs/dev/syntax.md
@@ -195,7 +195,7 @@ Modeling this with immutable trees is possible, but annoying.
195A function green tree is not super-convenient to use. 195A function green tree is not super-convenient to use.
196The biggest problem is accessing parents (there are no parent pointers!). 196The biggest problem is accessing parents (there are no parent pointers!).
197But there are also "identify" issues. 197But there are also "identify" issues.
198Let's say you want to write a code which builds a list of expressions in a file: `fn collect_exrepssions(file: GreenNode) -> HashSet<GreenNode>`. 198Let's say you want to write a code which builds a list of expressions in a file: `fn collect_expressions(file: GreenNode) -> HashSet<GreenNode>`.
199For the input like 199For the input like
200 200
201```rust 201```rust
@@ -236,7 +236,7 @@ impl SyntaxNode {
236 self.parent.clone() 236 self.parent.clone()
237 } 237 }
238 fn children(&self) -> impl Iterator<Item = SyntaxNode> { 238 fn children(&self) -> impl Iterator<Item = SyntaxNode> {
239 let mut offset = self.offset 239 let mut offset = self.offset;
240 self.green.children().map(|green_child| { 240 self.green.children().map(|green_child| {
241 let child_offset = offset; 241 let child_offset = offset;
242 offset += green_child.text_len; 242 offset += green_child.text_len;
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 83ef00058..a60d3668b 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -2414,27 +2414,27 @@
2414 "integrity": "sha512-1nG+6cuTtpzmXe7yYfO9GCkYlyV6Ai+jDnwidHiT2T7zhc+bJM+VTtc0T/CdTlDyTNTqIcCj0V1nD4TcVjJ7Ug==" 2414 "integrity": "sha512-1nG+6cuTtpzmXe7yYfO9GCkYlyV6Ai+jDnwidHiT2T7zhc+bJM+VTtc0T/CdTlDyTNTqIcCj0V1nD4TcVjJ7Ug=="
2415 }, 2415 },
2416 "vscode-languageclient": { 2416 "vscode-languageclient": {
2417 "version": "7.0.0-next.12", 2417 "version": "7.0.0-next.14",
2418 "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0-next.12.tgz", 2418 "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0-next.14.tgz",
2419 "integrity": "sha512-OrzvOvhS5o26C0KctTJC7hkwh3avCwkVhllzy42AqwpIUZ3p2aVqkSG2uVxaeodq8ThBb3TLgtg50vxyWs6FEg==", 2419 "integrity": "sha512-QUccfXK2F6AXXRFR8QJCaIz7N2BhJK6ok8E1aO8LHq2IBU33+5hTSJBXs7nEqrqZ/cY2VlDDbMWtMvCxz+/y1w==",
2420 "requires": { 2420 "requires": {
2421 "semver": "^6.3.0", 2421 "semver": "^6.3.0",
2422 "vscode-languageserver-protocol": "3.16.0-next.10" 2422 "vscode-languageserver-protocol": "3.16.0-next.11"
2423 } 2423 }
2424 }, 2424 },
2425 "vscode-languageserver-protocol": { 2425 "vscode-languageserver-protocol": {
2426 "version": "3.16.0-next.10", 2426 "version": "3.16.0-next.11",
2427 "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.10.tgz", 2427 "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.11.tgz",
2428 "integrity": "sha512-YRTctHUZvts0Z1xXKNYU0ha0o+Tlgtwr+6O8OmDquM086N8exiSKBMwMC+Ra1QtIE+1mfW43Wxsme2FnMkAS9A==", 2428 "integrity": "sha512-31FmupmSmfznuMuGp7qN6h3d/hKUbexbvcwTvrUE/igqRlzFU542s8MtGICx1ERbVuDOLGp96W2Z92qbUbmBPA==",
2429 "requires": { 2429 "requires": {
2430 "vscode-jsonrpc": "6.0.0-next.7", 2430 "vscode-jsonrpc": "6.0.0-next.7",
2431 "vscode-languageserver-types": "3.16.0-next.4" 2431 "vscode-languageserver-types": "3.16.0-next.5"
2432 } 2432 }
2433 }, 2433 },
2434 "vscode-languageserver-types": { 2434 "vscode-languageserver-types": {
2435 "version": "3.16.0-next.4", 2435 "version": "3.16.0-next.5",
2436 "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.4.tgz", 2436 "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.5.tgz",
2437 "integrity": "sha512-NlKJyGcET/ZBCCLBYIPaGo2c37R03bPYeWXozUtnjyye7+9dhlbMSODyoG2INcQf8zFmB4qhm2UOJjgYEgPCNA==" 2437 "integrity": "sha512-lf8Y1XXMtF1r2oDDAmJe+drizNXkybSRXAQQk5dPy2rYJsY9SPXYNO074L3THu9zNYepzV5fRJZUPo/V/TLBRQ=="
2438 }, 2438 },
2439 "vscode-test": { 2439 "vscode-test": {
2440 "version": "1.4.0", 2440 "version": "1.4.0",
diff --git a/editors/code/package.json b/editors/code/package.json
index fa8cf90f8..c3f1a0d8d 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -21,7 +21,7 @@
21 "Programming Languages" 21 "Programming Languages"
22 ], 22 ],
23 "engines": { 23 "engines": {
24 "vscode": "^1.47.1" 24 "vscode": "^1.51.0"
25 }, 25 },
26 "enableProposedApi": true, 26 "enableProposedApi": true,
27 "scripts": { 27 "scripts": {
@@ -36,7 +36,7 @@
36 }, 36 },
37 "dependencies": { 37 "dependencies": {
38 "node-fetch": "^2.6.1", 38 "node-fetch": "^2.6.1",
39 "vscode-languageclient": "7.0.0-next.12" 39 "vscode-languageclient": "7.0.0-next.14"
40 }, 40 },
41 "devDependencies": { 41 "devDependencies": {
42 "@rollup/plugin-commonjs": "^13.0.2", 42 "@rollup/plugin-commonjs": "^13.0.2",
@@ -187,6 +187,11 @@
187 "command": "rust-analyzer.openDocs", 187 "command": "rust-analyzer.openDocs",
188 "title": "Open docs under cursor", 188 "title": "Open docs under cursor",
189 "category": "Rust Analyzer" 189 "category": "Rust Analyzer"
190 },
191 {
192 "command": "rust-analyzer.openCargoToml",
193 "title": "Open Cargo.toml",
194 "category": "Rust Analyzer"
190 } 195 }
191 ], 196 ],
192 "keybindings": [ 197 "keybindings": [
@@ -278,6 +283,11 @@
278 "default": null, 283 "default": null,
279 "description": "Specify the compilation target" 284 "description": "Specify the compilation target"
280 }, 285 },
286 "rust-analyzer.noSysroot": {
287 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
288 "type": "boolean",
289 "default": false
290 },
281 "rust-analyzer.rustfmt.extraArgs": { 291 "rust-analyzer.rustfmt.extraArgs": {
282 "type": "array", 292 "type": "array",
283 "items": { 293 "items": {
@@ -450,6 +460,11 @@
450 "default": true, 460 "default": true,
451 "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc." 461 "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc."
452 }, 462 },
463 "rust-analyzer.completion.enableExperimental": {
464 "type": "boolean",
465 "default": true,
466 "markdownDescription": "Display additional completions with potential false positives and performance issues"
467 },
453 "rust-analyzer.callInfo.full": { 468 "rust-analyzer.callInfo.full": {
454 "type": "boolean", 469 "type": "boolean",
455 "default": true, 470 "default": true,
@@ -600,11 +615,6 @@
600 }, 615 },
601 "default": null 616 "default": null
602 }, 617 },
603 "rust-analyzer.withSysroot": {
604 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
605 "type": "boolean",
606 "default": true
607 },
608 "rust-analyzer.diagnostics.enable": { 618 "rust-analyzer.diagnostics.enable": {
609 "type": "boolean", 619 "type": "boolean",
610 "default": true, 620 "default": true,
@@ -687,6 +697,14 @@
687 }, 697 },
688 "default": [], 698 "default": [],
689 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'" 699 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'"
700 },
701 "rust-analyzer.rustcSource": {
702 "type": [
703 "null",
704 "string"
705 ],
706 "default": null,
707 "description": "Path to the rust compiler sources, for usage in rustc_private projects."
690 } 708 }
691 } 709 }
692 }, 710 },
@@ -1054,6 +1072,10 @@
1054 { 1072 {
1055 "command": "rust-analyzer.openDocs", 1073 "command": "rust-analyzer.openDocs",
1056 "when": "inRustProject" 1074 "when": "inRustProject"
1075 },
1076 {
1077 "command": "rust-analyzer.openCargoToml",
1078 "when": "inRustProject"
1057 } 1079 }
1058 ] 1080 ]
1059 } 1081 }
diff --git a/editors/code/rust.tmGrammar.json b/editors/code/rust.tmGrammar.json
index 608a3354e..4759bb116 100644
--- a/editors/code/rust.tmGrammar.json
+++ b/editors/code/rust.tmGrammar.json
@@ -167,7 +167,7 @@
167 "match": "(mod)\\s+((?:r#(?!crate|[Ss]elf|super))?[a-z][A-Za-z0-9_]*)", 167 "match": "(mod)\\s+((?:r#(?!crate|[Ss]elf|super))?[a-z][A-Za-z0-9_]*)",
168 "captures": { 168 "captures": {
169 "1": { 169 "1": {
170 "name": "keyword.control.rust" 170 "name": "storage.type.rust"
171 }, 171 },
172 "2": { 172 "2": {
173 "name": "entity.name.module.rust" 173 "name": "entity.name.module.rust"
@@ -180,7 +180,7 @@
180 "begin": "\\b(extern)\\s+(crate)", 180 "begin": "\\b(extern)\\s+(crate)",
181 "beginCaptures": { 181 "beginCaptures": {
182 "1": { 182 "1": {
183 "name": "keyword.control.rust" 183 "name": "storage.type.rust"
184 }, 184 },
185 "2": { 185 "2": {
186 "name": "keyword.other.crate.rust" 186 "name": "keyword.other.crate.rust"
@@ -213,7 +213,7 @@
213 "begin": "\\b(use)\\s", 213 "begin": "\\b(use)\\s",
214 "beginCaptures": { 214 "beginCaptures": {
215 "1": { 215 "1": {
216 "name": "keyword.control.rust" 216 "name": "keyword.other.rust"
217 } 217 }
218 }, 218 },
219 "end": ";", 219 "end": ";",
@@ -307,9 +307,14 @@
307 "block-comments": { 307 "block-comments": {
308 "patterns": [ 308 "patterns": [
309 { 309 {
310 "comment": "block comments", 310 "comment": "empty block comments",
311 "name": "comment.block.rust", 311 "name": "comment.block.rust",
312 "begin": "/\\*(?!\\*)", 312 "match": "/\\*\\*/"
313 },
314 {
315 "comment": "block documentation comments",
316 "name": "comment.block.documentation.rust",
317 "begin": "/\\*\\*",
313 "end": "\\*/", 318 "end": "\\*/",
314 "patterns": [ 319 "patterns": [
315 { 320 {
@@ -318,9 +323,9 @@
318 ] 323 ]
319 }, 324 },
320 { 325 {
321 "comment": "block documentation comments", 326 "comment": "block comments",
322 "name": "comment.block.documentation.rust", 327 "name": "comment.block.rust",
323 "begin": "/\\*\\*", 328 "begin": "/\\*(?!\\*)",
324 "end": "\\*/", 329 "end": "\\*/",
325 "patterns": [ 330 "patterns": [
326 { 331 {
@@ -342,7 +347,7 @@
342 "match": "\\b(const)\\s+([A-Z][A-Za-z0-9_]*)\\b", 347 "match": "\\b(const)\\s+([A-Z][A-Za-z0-9_]*)\\b",
343 "captures": { 348 "captures": {
344 "1": { 349 "1": {
345 "name": "keyword.control.rust" 350 "name": "storage.type.rust"
346 }, 351 },
347 "2": { 352 "2": {
348 "name": "constant.other.caps.rust" 353 "name": "constant.other.caps.rust"
@@ -404,7 +409,7 @@
404 { 409 {
405 "comment": "booleans", 410 "comment": "booleans",
406 "name": "constant.language.bool.rust", 411 "name": "constant.language.bool.rust",
407 "match": "\\btrue|false\\b" 412 "match": "\\b(true|false)\\b"
408 } 413 }
409 ] 414 ]
410 }, 415 },
@@ -450,7 +455,7 @@
450 "begin": "\\b(fn)\\s+((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)((\\()|(<))", 455 "begin": "\\b(fn)\\s+((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)((\\()|(<))",
451 "beginCaptures": { 456 "beginCaptures": {
452 "1": { 457 "1": {
453 "name": "keyword.control.fn.rust" 458 "name": "keyword.other.fn.rust"
454 }, 459 },
455 "2": { 460 "2": {
456 "name": "entity.name.function.rust" 461 "name": "entity.name.function.rust"
@@ -643,7 +648,7 @@
643 { 648 {
644 "comment": "control flow keywords", 649 "comment": "control flow keywords",
645 "name": "keyword.control.rust", 650 "name": "keyword.control.rust",
646 "match": "\\b(async|await|break|continue|do|else|for|if|loop|match|move|return|try|where|while|yield)\\b" 651 "match": "\\b(await|break|continue|do|else|for|if|loop|match|return|try|while|yield)\\b"
647 }, 652 },
648 { 653 {
649 "comment": "storage keywords", 654 "comment": "storage keywords",
@@ -658,7 +663,7 @@
658 { 663 {
659 "comment": "other keywords", 664 "comment": "other keywords",
660 "name": "keyword.other.rust", 665 "name": "keyword.other.rust",
661 "match": "\\b(as|become|box|dyn|final|impl|in|override|priv|pub|ref|typeof|union|unsafe|unsized|use|virtual)\\b" 666 "match": "\\b(as|async|become|box|dyn|move|final|impl|in|override|priv|pub|ref|typeof|union|unsafe|unsized|use|virtual|where)\\b"
662 }, 667 },
663 { 668 {
664 "comment": "fn", 669 "comment": "fn",
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index d032b45b7..63ab82dde 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -4,6 +4,7 @@ import * as ra from '../src/lsp_ext';
4import * as Is from 'vscode-languageclient/lib/common/utils/is'; 4import * as Is from 'vscode-languageclient/lib/common/utils/is';
5import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens'; 5import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens';
6import { assert } from './util'; 6import { assert } from './util';
7import { WorkspaceEdit } from 'vscode';
7 8
8function renderCommand(cmd: ra.CommandLink) { 9function renderCommand(cmd: ra.CommandLink) {
9 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; 10 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
@@ -75,8 +76,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
75 return Promise.resolve(null); 76 return Promise.resolve(null);
76 }); 77 });
77 }, 78 },
78 // Using custom handling of CodeActions where each code action is resolved lazily 79 // Using custom handling of CodeActions to support action groups and snippet edits.
79 // That's why we are not waiting for any command or edits 80 // Note that this means we have to re-implement lazy edit resolving ourselves as well.
80 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { 81 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
81 const params: lc.CodeActionParams = { 82 const params: lc.CodeActionParams = {
82 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), 83 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
@@ -99,16 +100,15 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
99 const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind); 100 const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
100 const action = new vscode.CodeAction(item.title, kind); 101 const action = new vscode.CodeAction(item.title, kind);
101 const group = (item as any).group; 102 const group = (item as any).group;
102 const id = (item as any).id;
103 const resolveParams: ra.ResolveCodeActionParams = {
104 id: id,
105 codeActionParams: params
106 };
107 action.command = { 103 action.command = {
108 command: "rust-analyzer.resolveCodeAction", 104 command: "rust-analyzer.resolveCodeAction",
109 title: item.title, 105 title: item.title,
110 arguments: [resolveParams], 106 arguments: [item],
111 }; 107 };
108
109 // Set a dummy edit, so that VS Code doesn't try to resolve this.
110 action.edit = new WorkspaceEdit();
111
112 if (group) { 112 if (group) {
113 let entry = groups.get(group); 113 let entry = groups.get(group);
114 if (!entry) { 114 if (!entry) {
@@ -134,6 +134,10 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
134 return { label: item.title, arguments: item.command!!.arguments!![0] }; 134 return { label: item.title, arguments: item.command!!.arguments!![0] };
135 })], 135 })],
136 }; 136 };
137
138 // Set a dummy edit, so that VS Code doesn't try to resolve this.
139 action.edit = new WorkspaceEdit();
140
137 result[index] = action; 141 result[index] = action;
138 } 142 }
139 } 143 }
@@ -164,13 +168,14 @@ class ExperimentalFeatures implements lc.StaticFeature {
164 const caps: any = capabilities.experimental ?? {}; 168 const caps: any = capabilities.experimental ?? {};
165 caps.snippetTextEdit = true; 169 caps.snippetTextEdit = true;
166 caps.codeActionGroup = true; 170 caps.codeActionGroup = true;
167 caps.resolveCodeAction = true;
168 caps.hoverActions = true; 171 caps.hoverActions = true;
169 caps.statusNotification = true; 172 caps.statusNotification = true;
170 capabilities.experimental = caps; 173 capabilities.experimental = caps;
171 } 174 }
172 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 175 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
173 } 176 }
177 dispose(): void {
178 }
174} 179}
175 180
176function isCodeActionWithoutEditsAndCommands(value: any): boolean { 181function isCodeActionWithoutEditsAndCommands(value: any): boolean {
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 22509e874..92bc4d7f7 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -188,6 +188,27 @@ export function parentModule(ctx: Ctx): Cmd {
188 }; 188 };
189} 189}
190 190
191export function openCargoToml(ctx: Ctx): Cmd {
192 return async () => {
193 const editor = ctx.activeRustEditor;
194 const client = ctx.client;
195 if (!editor || !client) return;
196
197 const response = await client.sendRequest(ra.openCargoToml, {
198 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
199 });
200 if (!response) return;
201
202 const uri = client.protocol2CodeConverter.asUri(response.uri);
203 const range = client.protocol2CodeConverter.asRange(response.range);
204
205 const doc = await vscode.workspace.openTextDocument(uri);
206 const e = await vscode.window.showTextDocument(doc);
207 e.selection = new vscode.Selection(range.start, range.start);
208 e.revealRange(range, vscode.TextEditorRevealType.InCenter);
209 };
210}
211
191export function ssr(ctx: Ctx): Cmd { 212export function ssr(ctx: Ctx): Cmd {
192 return async () => { 213 return async () => {
193 const editor = vscode.window.activeTextEditor; 214 const editor = vscode.window.activeTextEditor;
@@ -395,7 +416,7 @@ export function showReferences(ctx: Ctx): Cmd {
395} 416}
396 417
397export function applyActionGroup(_ctx: Ctx): Cmd { 418export function applyActionGroup(_ctx: Ctx): Cmd {
398 return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => { 419 return async (actions: { label: string; arguments: lc.CodeAction }[]) => {
399 const selectedAction = await vscode.window.showQuickPick(actions); 420 const selectedAction = await vscode.window.showQuickPick(actions);
400 if (!selectedAction) return; 421 if (!selectedAction) return;
401 vscode.commands.executeCommand( 422 vscode.commands.executeCommand(
@@ -442,12 +463,13 @@ export function openDocs(ctx: Ctx): Cmd {
442 463
443export function resolveCodeAction(ctx: Ctx): Cmd { 464export function resolveCodeAction(ctx: Ctx): Cmd {
444 const client = ctx.client; 465 const client = ctx.client;
445 return async (params: ra.ResolveCodeActionParams) => { 466 return async (params: lc.CodeAction) => {
446 const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); 467 params.command = undefined;
447 if (!item) { 468 const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params);
469 if (!item.edit) {
448 return; 470 return;
449 } 471 }
450 const edit = client.protocol2CodeConverter.asWorkspaceEdit(item); 472 const edit = client.protocol2CodeConverter.asWorkspaceEdit(item.edit);
451 await applySnippetWorkspaceEdit(edit); 473 await applySnippetWorkspaceEdit(edit);
452 }; 474 };
453} 475}
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index fc8e120b3..5e877ce65 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -43,12 +43,6 @@ export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position
43 43
44export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule"); 44export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule");
45 45
46export interface ResolveCodeActionParams {
47 id: string;
48 codeActionParams: lc.CodeActionParams;
49}
50export const resolveCodeAction = new lc.RequestType<ResolveCodeActionParams, lc.WorkspaceEdit, unknown>('experimental/resolveCodeAction');
51
52export interface JoinLinesParams { 46export interface JoinLinesParams {
53 textDocument: lc.TextDocumentIdentifier; 47 textDocument: lc.TextDocumentIdentifier;
54 ranges: lc.Range[]; 48 ranges: lc.Range[];
@@ -120,3 +114,9 @@ export interface CommandLinkGroup {
120} 114}
121 115
122export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs'); 116export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs');
117
118export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>("experimental/openCargoToml");
119
120export interface OpenCargoTomlParams {
121 textDocument: lc.TextDocumentIdentifier;
122}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 09543e348..2f3dde8ac 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -111,6 +111,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
111 ctx.registerCommand('debug', commands.debug); 111 ctx.registerCommand('debug', commands.debug);
112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig); 112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
113 ctx.registerCommand('openDocs', commands.openDocs); 113 ctx.registerCommand('openDocs', commands.openDocs);
114 ctx.registerCommand('openCargoToml', commands.openCargoToml);
114 115
115 defaultOnEnter.dispose(); 116 defaultOnEnter.dispose();
116 ctx.registerCommand('onEnter', commands.onEnter); 117 ctx.registerCommand('onEnter', commands.onEnter);
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index 258b49982..fee736e7d 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -3,16 +3,29 @@ import * as vscode from 'vscode';
3import { assert } from './util'; 3import { assert } from './util';
4 4
5export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) { 5export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
6 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`); 6 if (edit.entries().length === 1) {
7 const [uri, edits] = edit.entries()[0]; 7 const [uri, edits] = edit.entries()[0];
8 const editor = await editorFromUri(uri);
9 if (editor) await applySnippetTextEdits(editor, edits);
10 return;
11 }
12 for (const [uri, edits] of edit.entries()) {
13 const editor = await editorFromUri(uri);
14 if (editor) await editor.edit((builder) => {
15 for (const indel of edits) {
16 assert(!parseSnippet(indel.newText), `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`);
17 builder.replace(indel.range, indel.newText);
18 }
19 });
20 }
21}
8 22
23async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undefined> {
9 if (vscode.window.activeTextEditor?.document.uri !== uri) { 24 if (vscode.window.activeTextEditor?.document.uri !== uri) {
10 // `vscode.window.visibleTextEditors` only contains editors whose contents are being displayed 25 // `vscode.window.visibleTextEditors` only contains editors whose contents are being displayed
11 await vscode.window.showTextDocument(uri, {}); 26 await vscode.window.showTextDocument(uri, {});
12 } 27 }
13 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString()); 28 return vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
14 if (!editor) return;
15 await applySnippetTextEdits(editor, edits);
16} 29}
17 30
18export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) { 31export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 9de60c76c..4c58aed59 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -168,6 +168,7 @@ ISC
168MIT 168MIT
169MIT / Apache-2.0 169MIT / Apache-2.0
170MIT OR Apache-2.0 170MIT OR Apache-2.0
171MIT OR Apache-2.0 OR Zlib
171MIT OR Zlib OR Apache-2.0 172MIT OR Zlib OR Apache-2.0
172MIT/Apache-2.0 173MIT/Apache-2.0
173Unlicense OR MIT 174Unlicense OR MIT
@@ -214,8 +215,6 @@ fn check_todo(path: &Path, text: &str) {
214 // This file itself obviously needs to use todo (<- like this!). 215 // This file itself obviously needs to use todo (<- like this!).
215 "tests/cli.rs", 216 "tests/cli.rs",
216 // Some of our assists generate `todo!()`. 217 // Some of our assists generate `todo!()`.
217 "tests/generated.rs",
218 "handlers/add_missing_impl_members.rs",
219 "handlers/add_turbo_fish.rs", 218 "handlers/add_turbo_fish.rs",
220 "handlers/generate_function.rs", 219 "handlers/generate_function.rs",
221 // To support generating `todo!()` in assists, we have `expr_todo()` in 220 // To support generating `todo!()` in assists, we have `expr_todo()` in
@@ -228,6 +227,11 @@ fn check_todo(path: &Path, text: &str) {
228 return; 227 return;
229 } 228 }
230 if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") { 229 if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
230 // Generated by an assist
231 if text.contains("${0:todo!()}") {
232 return;
233 }
234
231 panic!( 235 panic!(
232 "\nTODO markers or todo! macros should not be committed to the master branch,\n\ 236 "\nTODO markers or todo! macros should not be committed to the master branch,\n\
233 use FIXME instead\n\ 237 use FIXME instead\n\