diff options
94 files changed, 3093 insertions, 1203 deletions
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3c36c4fb84a..b2e5db6f689 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -31,13 +31,13 @@ jobs: - os: windows-latest target: aarch64-pc-windows-msvc code-target: win32-arm64 - - os: ubuntu-18.04 + - os: ubuntu-20.04 target: x86_64-unknown-linux-gnu code-target: linux-x64 - - os: ubuntu-18.04 + - os: ubuntu-20.04 target: aarch64-unknown-linux-gnu code-target: linux-arm64 - - os: ubuntu-18.04 + - os: ubuntu-20.04 target: arm-unknown-linux-gnueabihf code-target: linux-armhf - os: macos-11 diff --git a/Cargo.lock b/Cargo.lock index 7c6796d70bd..9f10d92c4e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" [[package]] name = "anymap" @@ -78,16 +78,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.28.4", + "object", "rustc-demangle", ] @@ -114,9 +114,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "camino" -version = "1.0.9" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" dependencies = [ "serde", ] @@ -171,9 +171,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chalk-derive" -version = "0.83.0" +version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83553c2ef7717e58aecdf42dd9e3c876229f5a1f35a16435b5ddc4addef81827" +checksum = "cf29c109d57f8d57b0e7675391be37a9285d86dd93278bd5f14a0ad3c447a6c2" dependencies = [ "proc-macro2", "quote", @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "chalk-ir" -version = "0.83.0" +version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd42107d579d8ec2a5af20a8de62a37524a67bf6a4c0ff08a950068f0bfea91" +checksum = "d391763027b5e50a5e15caf6d2857ec585fd68160367bbeac9e1804209620918" dependencies = [ "bitflags", "chalk-derive", @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "chalk-recursive" -version = "0.83.0" +version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c444031541a76c13c145e76d91f1548e9feb2240e7f0c3e77879ceb694994f2d" +checksum = "afafd92dcdc7fe0ea940ee94bdd8cc5bd18f4a4a84c593d6d7025fe16c150478" dependencies = [ "chalk-derive", "chalk-ir", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "chalk-solve" -version = "0.83.0" +version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76f2db19c5e8a3d42340cf5b4d90b8c218750536fca35e2bb285ab6653c0bc8" +checksum = "3af1d111f11c91c48ace02e93e470c5bae6d2631bd112e4545317da53660d7fc" dependencies = [ "chalk-derive", "chalk-ir", @@ -248,24 +248,10 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" -dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", @@ -273,9 +259,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -284,9 +270,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg", "cfg-if", @@ -297,20 +283,10 @@ dependencies = [ ] [[package]] -name = "crossbeam-queue" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if", "once_cell", @@ -359,9 +335,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "ena" @@ -458,15 +434,15 @@ checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" @@ -728,6 +704,7 @@ dependencies = [ "ide-db", "itertools", "parser", + "stdx", "syntax", "test-utils", "text-edit", @@ -794,9 +771,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jod-thread" @@ -836,9 +813,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libloading" @@ -895,9 +872,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.93.0" +version = "0.93.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c74e2173b2b31f8655d33724b4b45ac13f439386f66290f539c22b144c2212" +checksum = "a3bcfee315dde785ba887edb540b08765fd7df75a7d948844be6bf5712246734" dependencies = [ "bitflags", "serde", @@ -944,9 +921,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -1001,9 +978,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0-pre.15" +version = "5.0.0-pre.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553f9844ad0b0824605c20fb55a661679782680410abfb1a8144c2e7e437e7a7" +checksum = "530f6314d6904508082f4ea424a0275cf62d341e118b313663f266429cb19693" dependencies = [ "bitflags", "crossbeam-channel", @@ -1029,15 +1006,6 @@ dependencies = [ [[package]] name = "object" -version = "0.28.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" -dependencies = [ - "memchr", -] - -[[package]] -name = "object" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" @@ -1047,9 +1015,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "oorandom" @@ -1118,9 +1086,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "paths" @@ -1172,7 +1140,7 @@ name = "proc-macro-api" version = "0.0.0" dependencies = [ "memmap2", - "object 0.29.0", + "object", "paths", "profile", "serde", @@ -1187,12 +1155,11 @@ dependencies = [ name = "proc-macro-srv" version = "0.0.0" dependencies = [ - "crossbeam", "expect-test", "libloading", "mbe", "memmap2", - "object 0.29.0", + "object", "paths", "proc-macro-api", "proc-macro-test", @@ -1221,9 +1188,9 @@ version = "0.0.0" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -1264,10 +1231,30 @@ dependencies = [ ] [[package]] +name = "protobuf" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee4a7d8b91800c8f167a6268d1a1026607368e1adc84e98fe044aeb905302f7" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-support" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca157fe12fc7ee2e315f2f735e27df41b3d97cdd70ea112824dac1ffb08ee1c" +dependencies = [ + "thiserror", +] + +[[package]] name = "pulldown-cmark" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" +checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" dependencies = [ "bitflags", "memchr", @@ -1285,9 +1272,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -1318,18 +1305,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "regex-syntax", ] @@ -1345,9 +1332,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "rowan" @@ -1394,6 +1381,7 @@ dependencies = [ "project-model", "rayon", "rustc-hash", + "scip", "serde", "serde_json", "sourcegen", @@ -1438,9 +1426,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "salsa" @@ -1481,6 +1469,15 @@ dependencies = [ ] [[package]] +name = "scip" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bfbb10286f69fad7c78db71004b7839bf957788359fe0c479f029f9849136b" +dependencies = [ + "protobuf", +] + +[[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1494,27 +1491,27 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.138" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.138" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -1523,9 +1520,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "indexmap", "itoa", @@ -1535,9 +1532,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", @@ -1594,9 +1591,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -1666,6 +1663,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" [[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "thread_local" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1739,9 +1756,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if", "pin-project-lite", @@ -1762,9 +1779,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -1783,9 +1800,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "matchers", "once_cell", @@ -1905,6 +1922,7 @@ dependencies = [ "indexmap", "paths", "rustc-hash", + "stdx", ] [[package]] diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 9580ce8007c..b388e47dee6 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -9,10 +9,11 @@ use std::{fmt, ops, panic::RefUnwindSafe, str::FromStr, sync::Arc}; use cfg::CfgOptions; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; +use stdx::hash::{NoHashHashMap, NoHashHashSet}; use syntax::SmolStr; use tt::Subtree; -use vfs::{file_set::FileSet, FileId, VfsPath}; +use vfs::{file_set::FileSet, AnchoredPath, FileId, VfsPath}; /// Files are grouped into source roots. A source root is a directory on the /// file systems which is watched for changes. Typically it corresponds to a @@ -31,22 +32,30 @@ pub struct SourceRoot { /// Libraries are considered mostly immutable, this assumption is used to /// optimize salsa's query structure pub is_library: bool, - pub(crate) file_set: FileSet, + file_set: FileSet, } impl SourceRoot { pub fn new_local(file_set: FileSet) -> SourceRoot { SourceRoot { is_library: false, file_set } } + pub fn new_library(file_set: FileSet) -> SourceRoot { SourceRoot { is_library: true, file_set } } + pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { self.file_set.path_for_file(file) } + pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> { self.file_set.file_for_path(path) } + + pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { + self.file_set.resolve_path(path) + } + pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { self.file_set.iter() } @@ -72,12 +81,19 @@ impl SourceRoot { /// <https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/architecture.md#serialization> #[derive(Debug, Clone, Default /* Serialize, Deserialize */)] pub struct CrateGraph { - arena: FxHashMap<CrateId, CrateData>, + arena: NoHashHashMap<CrateId, CrateData>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct CrateId(pub u32); +impl stdx::hash::NoHashHashable for CrateId {} +impl std::hash::Hash for CrateId { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.0.hash(state); + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CrateName(SmolStr); @@ -342,7 +358,7 @@ impl CrateGraph { // Check if adding a dep from `from` to `to` creates a cycle. To figure // that out, look for a path in the *opposite* direction, from `to` to // `from`. - if let Some(path) = self.find_path(&mut FxHashSet::default(), dep.crate_id, from) { + if let Some(path) = self.find_path(&mut NoHashHashSet::default(), dep.crate_id, from) { let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect(); let err = CyclicDependenciesError { path }; assert!(err.from().0 == from && err.to().0 == dep.crate_id); @@ -365,7 +381,7 @@ impl CrateGraph { /// including the crate itself. pub fn transitive_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> { let mut worklist = vec![of]; - let mut deps = FxHashSet::default(); + let mut deps = NoHashHashSet::default(); while let Some(krate) = worklist.pop() { if !deps.insert(krate) { @@ -382,10 +398,10 @@ impl CrateGraph { /// including the crate itself. pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> { let mut worklist = vec![of]; - let mut rev_deps = FxHashSet::default(); + let mut rev_deps = NoHashHashSet::default(); rev_deps.insert(of); - let mut inverted_graph = FxHashMap::<_, Vec<_>>::default(); + let mut inverted_graph = NoHashHashMap::<_, Vec<_>>::default(); self.arena.iter().for_each(|(&krate, data)| { data.dependencies .iter() @@ -409,7 +425,7 @@ impl CrateGraph { /// come before the crate itself). pub fn crates_in_topological_order(&self) -> Vec<CrateId> { let mut res = Vec::new(); - let mut visited = FxHashSet::default(); + let mut visited = NoHashHashSet::default(); for krate in self.arena.keys().copied() { go(self, &mut visited, &mut res, krate); @@ -419,7 +435,7 @@ impl CrateGraph { fn go( graph: &CrateGraph, - visited: &mut FxHashSet<CrateId>, + visited: &mut NoHashHashSet<CrateId>, res: &mut Vec<CrateId>, source: CrateId, ) { @@ -459,7 +475,7 @@ impl CrateGraph { fn find_path( &self, - visited: &mut FxHashSet<CrateId>, + visited: &mut NoHashHashSet<CrateId>, from: CrateId, to: CrateId, ) -> Option<Vec<CrateId>> { diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs index 2d0a95b09d9..da11e4ae7bb 100644 --- a/crates/base-db/src/lib.rs +++ b/crates/base-db/src/lib.rs @@ -8,7 +8,7 @@ pub mod fixture; use std::{panic, sync::Arc}; -use rustc_hash::FxHashSet; +use stdx::hash::NoHashHashSet; use syntax::{ast, Parse, SourceFile, TextRange, TextSize}; pub use crate::{ @@ -58,7 +58,7 @@ pub trait FileLoader { /// Text of the file. fn file_text(&self, file_id: FileId) -> Arc<String>; fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>; - fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>>; + fn relevant_crates(&self, file_id: FileId) -> Arc<NoHashHashSet<CrateId>>; } /// Database which stores all significant input facts: source code and project @@ -94,10 +94,10 @@ pub trait SourceDatabaseExt: SourceDatabase { #[salsa::input] fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>; - fn source_root_crates(&self, id: SourceRootId) -> Arc<FxHashSet<CrateId>>; + fn source_root_crates(&self, id: SourceRootId) -> Arc<NoHashHashSet<CrateId>>; } -fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<FxHashSet<CrateId>> { +fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<NoHashHashSet<CrateId>> { let graph = db.crate_graph(); let res = graph .iter() @@ -120,10 +120,10 @@ impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> { // FIXME: this *somehow* should be platform agnostic... let source_root = self.0.file_source_root(path.anchor); let source_root = self.0.source_root(source_root); - source_root.file_set.resolve_path(path) + source_root.resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { + fn relevant_crates(&self, file_id: FileId) -> Arc<NoHashHashSet<CrateId>> { let _p = profile::span("relevant_crates"); let source_root = self.0.file_source_root(file_id); self.0.source_root_crates(source_root) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 2bebea2fbfe..d9f4ef5b7ff 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -77,8 +77,13 @@ impl FlycheckHandle { } /// Schedule a re-start of the cargo check worker. - pub fn update(&self) { - self.sender.send(Restart).unwrap(); + pub fn restart(&self) { + self.sender.send(Restart::Yes).unwrap(); + } + + /// Stop this cargo check worker. + pub fn cancel(&self) { + self.sender.send(Restart::No).unwrap(); } pub fn id(&self) -> usize { @@ -120,9 +125,13 @@ pub enum Progress { DidCheckCrate(String), DidFinish(io::Result<()>), DidCancel, + DidFailToRestart(String), } -struct Restart; +enum Restart { + Yes, + No, +} struct FlycheckActor { id: usize, @@ -149,6 +158,7 @@ impl FlycheckActor { config: FlycheckConfig, workspace_root: AbsPathBuf, ) -> FlycheckActor { + tracing::info!(%id, ?workspace_root, "Spawning flycheck"); FlycheckActor { id, sender, config, workspace_root, cargo_handle: None } } fn progress(&self, progress: Progress) { @@ -164,10 +174,13 @@ impl FlycheckActor { fn run(mut self, inbox: Receiver<Restart>) { while let Some(event) = self.next_event(&inbox) { match event { - Event::Restart(Restart) => { + Event::Restart(Restart::No) => { + self.cancel_check_process(); + } + Event::Restart(Restart::Yes) => { // Cancel the previously spawned process self.cancel_check_process(); - while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {} + while let Ok(_) = inbox.recv_timeout(Duration::from_millis(50)) {} let command = self.check_command(); tracing::debug!(?command, "will restart flycheck"); @@ -181,10 +194,11 @@ impl FlycheckActor { self.progress(Progress::DidStart); } Err(error) => { - tracing::error!( - command = ?self.check_command(), - %error, "failed to restart flycheck" - ); + self.progress(Progress::DidFailToRestart(format!( + "Failed to run the following command: {:?} error={}", + self.check_command(), + error + ))); } } } @@ -223,6 +237,10 @@ impl FlycheckActor { fn cancel_check_process(&mut self) { if let Some(cargo_handle) = self.cargo_handle.take() { + tracing::debug!( + command = ?self.check_command(), + "did cancel flycheck" + ); cargo_handle.cancel(); self.progress(Progress::DidCancel); } diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 35c8708955a..631ae3cf11f 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, MacroCallId, MacroDefKind}; +use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroDefKind}; use smallvec::SmallVec; use syntax::ast; @@ -12,7 +12,10 @@ use crate::{ db::DefDatabase, intern::Interned, item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId}, - nameres::{attr_resolution::ResolvedAttr, proc_macro::ProcMacroKind, DefMap}, + nameres::{ + attr_resolution::ResolvedAttr, diagnostics::DefDiagnostic, proc_macro::ProcMacroKind, + DefMap, + }, type_ref::{TraitRef, TypeBound, TypeRef}, visibility::RawVisibility, AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId, @@ -210,6 +213,13 @@ pub struct TraitData { impl TraitData { pub(crate) fn trait_data_query(db: &dyn DefDatabase, tr: TraitId) -> Arc<TraitData> { + db.trait_data_with_diagnostics(tr).0 + } + + pub(crate) fn trait_data_with_diagnostics_query( + db: &dyn DefDatabase, + tr: TraitId, + ) -> (Arc<TraitData>, Arc<Vec<DefDiagnostic>>) { let tr_loc @ ItemLoc { container: module_id, id: tree_id } = tr.lookup(db); let item_tree = tree_id.item_tree(db); let tr_def = &item_tree[tree_id.value]; @@ -229,17 +239,20 @@ impl TraitData { let mut collector = AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr)); collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items); - let (items, attribute_calls) = collector.finish(); - - Arc::new(TraitData { - name, - attribute_calls, - items, - is_auto, - is_unsafe, - visibility, - skip_array_during_method_dispatch, - }) + let (items, attribute_calls, diagnostics) = collector.finish(); + + ( + Arc::new(TraitData { + name, + attribute_calls, + items, + is_auto, + is_unsafe, + visibility, + skip_array_during_method_dispatch, + }), + Arc::new(diagnostics), + ) } pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ { @@ -280,7 +293,14 @@ pub struct ImplData { impl ImplData { pub(crate) fn impl_data_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplData> { - let _p = profile::span("impl_data_query"); + db.impl_data_with_diagnostics(id).0 + } + + pub(crate) fn impl_data_with_diagnostics_query( + db: &dyn DefDatabase, + id: ImplId, + ) -> (Arc<ImplData>, Arc<Vec<DefDiagnostic>>) { + let _p = profile::span("impl_data_with_diagnostics_query"); let ItemLoc { container: module_id, id: tree_id } = id.lookup(db); let item_tree = tree_id.item_tree(db); @@ -293,10 +313,13 @@ impl ImplData { AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::ImplId(id)); collector.collect(&item_tree, tree_id.tree_id(), &impl_def.items); - let (items, attribute_calls) = collector.finish(); + let (items, attribute_calls, diagnostics) = collector.finish(); let items = items.into_iter().map(|(_, item)| item).collect(); - Arc::new(ImplData { target_trait, self_ty, items, is_negative, attribute_calls }) + ( + Arc::new(ImplData { target_trait, self_ty, items, is_negative, attribute_calls }), + Arc::new(diagnostics), + ) } pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { @@ -437,6 +460,7 @@ struct AssocItemCollector<'a> { db: &'a dyn DefDatabase, module_id: ModuleId, def_map: Arc<DefMap>, + inactive_diagnostics: Vec<DefDiagnostic>, container: ItemContainerId, expander: Expander, @@ -459,15 +483,21 @@ impl<'a> AssocItemCollector<'a> { expander: Expander::new(db, file_id, module_id), items: Vec::new(), attr_calls: Vec::new(), + inactive_diagnostics: Vec::new(), } } fn finish( self, - ) -> (Vec<(Name, AssocItemId)>, Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>) { + ) -> ( + Vec<(Name, AssocItemId)>, + Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, + Vec<DefDiagnostic>, + ) { ( self.items, if self.attr_calls.is_empty() { None } else { Some(Box::new(self.attr_calls)) }, + self.inactive_diagnostics, ) } @@ -479,6 +509,12 @@ impl<'a> AssocItemCollector<'a> { 'items: for &item in assoc_items { let attrs = item_tree.attrs(self.db, self.module_id.krate, ModItem::from(item).into()); if !attrs.is_cfg_enabled(self.expander.cfg_options()) { + self.inactive_diagnostics.push(DefDiagnostic::unconfigured_code( + self.module_id.local_id, + InFile::new(self.expander.current_file_id(), item.ast_id(&item_tree).upcast()), + attrs.cfg().unwrap(), + self.expander.cfg_options().clone(), + )); continue; } diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index df6dcb024b5..40b2f734b71 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -20,7 +20,7 @@ use crate::{ intern::Interned, item_tree::{AttrOwner, ItemTree}, lang_item::{LangItemTarget, LangItems}, - nameres::DefMap, + nameres::{diagnostics::DefDiagnostic, DefMap}, visibility::{self, Visibility}, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalEnumVariantId, @@ -106,9 +106,16 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { #[salsa::invoke(ImplData::impl_data_query)] fn impl_data(&self, e: ImplId) -> Arc<ImplData>; + #[salsa::invoke(ImplData::impl_data_with_diagnostics_query)] + fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc<ImplData>, Arc<Vec<DefDiagnostic>>); + #[salsa::invoke(TraitData::trait_data_query)] fn trait_data(&self, e: TraitId) -> Arc<TraitData>; + #[salsa::invoke(TraitData::trait_data_with_diagnostics_query)] + fn trait_data_with_diagnostics(&self, tr: TraitId) + -> (Arc<TraitData>, Arc<Vec<DefDiagnostic>>); + #[salsa::invoke(TypeAliasData::type_alias_data_query)] fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>; diff --git a/crates/hir-def/src/nameres/diagnostics.rs b/crates/hir-def/src/nameres/diagnostics.rs index 0d01f6d0aba..ed7e920fd2b 100644 --- a/crates/hir-def/src/nameres/diagnostics.rs +++ b/crates/hir-def/src/nameres/diagnostics.rs @@ -73,7 +73,7 @@ impl DefDiagnostic { Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } } } - pub(super) fn unconfigured_code( + pub fn unconfigured_code( container: LocalModuleId, ast: AstId<ast::Item>, cfg: CfgExpr, diff --git a/crates/hir-def/src/nameres/mod_resolution.rs b/crates/hir-def/src/nameres/mod_resolution.rs index 99f7f1b549e..ca7bcc814e8 100644 --- a/crates/hir-def/src/nameres/mod_resolution.rs +++ b/crates/hir-def/src/nameres/mod_resolution.rs @@ -65,6 +65,7 @@ impl ModDir { name: &Name, attr_path: Option<&SmolStr>, ) -> Result<(FileId, bool, ModDir), Box<[String]>> { + let name = name.unescaped(); let orig_file_id = file_id.original_file(db.upcast()); let mut candidate_files = ArrayVec::<_, 2>::new(); @@ -73,12 +74,10 @@ impl ModDir { candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) } None if file_id.is_include_macro(db.upcast()) => { - let name = name.unescaped(); candidate_files.push(format!("{}.rs", name)); candidate_files.push(format!("{}/mod.rs", name)); } None => { - let name = name.unescaped(); candidate_files.push(format!("{}{}.rs", self.dir_path.0, name)); candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name)); } diff --git a/crates/hir-def/src/nameres/tests/mod_resolution.rs b/crates/hir-def/src/nameres/tests/mod_resolution.rs index 3fa585574de..ba3bf8b5a5c 100644 --- a/crates/hir-def/src/nameres/tests/mod_resolution.rs +++ b/crates/hir-def/src/nameres/tests/mod_resolution.rs @@ -127,7 +127,15 @@ mod r#async; use self::r#async::Bar; //- /async.rs +mod foo; +mod r#async; pub struct Bar; + +//- /async/foo.rs +pub struct Foo; + +//- /async/async.rs +pub struct Baz; "#, expect![[r#" crate @@ -136,6 +144,14 @@ pub struct Bar; crate::r#async Bar: t v + foo: t + r#async: t + + crate::r#async::foo + Foo: t v + + crate::r#async::r#async + Baz: t v "#]], ); } diff --git a/crates/hir-def/src/test_db.rs b/crates/hir-def/src/test_db.rs index 9cdc18d6b66..b7908bddaa1 100644 --- a/crates/hir-def/src/test_db.rs +++ b/crates/hir-def/src/test_db.rs @@ -10,7 +10,7 @@ use base_db::{ SourceDatabase, Upcast, }; use hir_expand::{db::AstDatabase, InFile}; -use rustc_hash::FxHashSet; +use stdx::hash::NoHashHashSet; use syntax::{algo, ast, AstNode}; use crate::{ @@ -76,7 +76,7 @@ impl FileLoader for TestDB { fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { FileLoaderDelegate(self).resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { + fn relevant_crates(&self, file_id: FileId) -> Arc<NoHashHashSet<CrateId>> { FileLoaderDelegate(self).relevant_crates(file_id) } } diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index 5cd444c1ad3..7f143f396c7 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -18,9 +18,9 @@ ena = "0.14.0" tracing = "0.1.35" rustc-hash = "1.1.0" scoped-tls = "1.0.0" -chalk-solve = { version = "0.83.0", default-features = false } -chalk-ir = "0.83.0" -chalk-recursive = { version = "0.83.0", default-features = false } +chalk-solve = { version = "0.84.0", default-features = false } +chalk-ir = "0.84.0" +chalk-recursive = { version = "0.84.0", default-features = false } la-arena = { version = "0.3.0", path = "../../lib/la-arena" } once_cell = "1.12.0" typed-arena = "2.0.1" diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 95a7229e879..5df48e5fdcb 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -734,6 +734,7 @@ impl<'a> InferenceContext<'a> { let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(strukt.into())); } + ValueNs::ImplSelf(impl_id) => (TypeNs::SelfType(impl_id), None), _ => return (self.err_ty(), None), }, Some(ResolveValueResult::Partial(typens, unresolved)) => (typens, Some(unresolved)), diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 64a07070f02..4a37a794533 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -6,7 +6,7 @@ //! //! This usually involves resolving names, collecting generic arguments etc. use std::{ - cell::{Cell, RefCell}, + cell::{Cell, RefCell, RefMut}, iter, sync::Arc, }; @@ -330,26 +330,29 @@ impl<'a> TyLoweringContext<'a> { } } TypeRef::Macro(macro_call) => { - let (expander, recursion_start) = { - let mut expander = self.expander.borrow_mut(); - if expander.is_some() { - (Some(expander), false) - } else { - *expander = Some(Expander::new( - self.db.upcast(), - macro_call.file_id, - self.resolver.module(), - )); - (Some(expander), true) + let (mut expander, recursion_start) = { + match RefMut::filter_map(self.expander.borrow_mut(), Option::as_mut) { + // There already is an expander here, this means we are already recursing + Ok(expander) => (expander, false), + // No expander was created yet, so we are at the start of the expansion recursion + // and therefore have to create an expander. + Err(expander) => ( + RefMut::map(expander, |it| { + it.insert(Expander::new( + self.db.upcast(), + macro_call.file_id, + self.resolver.module(), + )) + }), + true, + ), } }; - let ty = if let Some(mut expander) = expander { - let expander_mut = expander.as_mut().unwrap(); + let ty = { let macro_call = macro_call.to_node(self.db.upcast()); - match expander_mut.enter_expand::<ast::Type>(self.db.upcast(), macro_call) { + match expander.enter_expand::<ast::Type>(self.db.upcast(), macro_call) { Ok(ExpandResult { value: Some((mark, expanded)), .. }) => { - let ctx = - LowerCtx::new(self.db.upcast(), expander_mut.current_file_id()); + let ctx = LowerCtx::new(self.db.upcast(), expander.current_file_id()); let type_ref = TypeRef::from_ast(&ctx, expanded); drop(expander); @@ -362,11 +365,14 @@ impl<'a> TyLoweringContext<'a> { .exit(self.db.upcast(), mark); Some(ty) } - _ => None, + _ => { + drop(expander); + None + } } - } else { - None }; + + // drop the expander, resetting it to pre-recursion state if recursion_start { *self.expander.borrow_mut() = None; } @@ -479,7 +485,14 @@ impl<'a> TyLoweringContext<'a> { TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) } ParamLoweringMode::Variable => { - let idx = generics.param_idx(param_id.into()).expect("matching generics"); + let idx = match generics.param_idx(param_id.into()) { + None => { + never!("no matching generics"); + return (TyKind::Error.intern(Interner), None); + } + Some(idx) => idx, + }; + TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) } } diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 64622545f84..9a63d5013b4 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -1064,6 +1064,14 @@ pub fn resolve_indexing_op( None } +macro_rules! check_that { + ($cond:expr) => { + if !$cond { + return false; + } + }; +} + fn is_valid_candidate( table: &mut InferenceTable<'_>, name: Option<&Name>, @@ -1072,54 +1080,10 @@ fn is_valid_candidate( self_ty: &Ty, visible_from_module: Option<ModuleId>, ) -> bool { - macro_rules! check_that { - ($cond:expr) => { - if !$cond { - return false; - } - }; - } - let db = table.db; match item { AssocItemId::FunctionId(m) => { - let data = db.function_data(m); - - check_that!(name.map_or(true, |n| n == &data.name)); - check_that!(visible_from_module.map_or(true, |from_module| { - let v = db.function_visibility(m).is_visible_from(db.upcast(), from_module); - if !v { - cov_mark::hit!(autoderef_candidate_not_visible); - } - v - })); - - table.run_in_snapshot(|table| { - let subst = TyBuilder::subst_for_def(db, m).fill_with_inference_vars(table).build(); - let expect_self_ty = match m.lookup(db.upcast()).container { - ItemContainerId::TraitId(_) => { - subst.at(Interner, 0).assert_ty_ref(Interner).clone() - } - ItemContainerId::ImplId(impl_id) => { - subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner) - } - // We should only get called for associated items (impl/trait) - ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { - unreachable!() - } - }; - check_that!(table.unify(&expect_self_ty, self_ty)); - if let Some(receiver_ty) = receiver_ty { - check_that!(data.has_self_param()); - - let sig = db.callable_item_signature(m.into()); - let expected_receiver = - sig.map(|s| s.params()[0].clone()).substitute(Interner, &subst); - - check_that!(table.unify(&receiver_ty, &expected_receiver)); - } - true - }) + is_valid_fn_candidate(table, m, name, receiver_ty, self_ty, visible_from_module) } AssocItemId::ConstId(c) => { let data = db.const_data(c); @@ -1152,6 +1116,94 @@ fn is_valid_candidate( } } +fn is_valid_fn_candidate( + table: &mut InferenceTable<'_>, + fn_id: FunctionId, + name: Option<&Name>, + receiver_ty: Option<&Ty>, + self_ty: &Ty, + visible_from_module: Option<ModuleId>, +) -> bool { + let db = table.db; + let data = db.function_data(fn_id); + + check_that!(name.map_or(true, |n| n == &data.name)); + check_that!(visible_from_module.map_or(true, |from_module| { + let v = db.function_visibility(fn_id).is_visible_from(db.upcast(), from_module); + if !v { + cov_mark::hit!(autoderef_candidate_not_visible); + } + v + })); + + table.run_in_snapshot(|table| { + let container = fn_id.lookup(db.upcast()).container; + let impl_subst = match container { + ItemContainerId::ImplId(it) => { + TyBuilder::subst_for_def(db, it).fill_with_inference_vars(table).build() + } + ItemContainerId::TraitId(it) => { + TyBuilder::subst_for_def(db, it).fill_with_inference_vars(table).build() + } + _ => unreachable!(), + }; + + let fn_subst = TyBuilder::subst_for_def(db, fn_id) + .use_parent_substs(&impl_subst) + .fill_with_inference_vars(table) + .build(); + + let expect_self_ty = match container { + ItemContainerId::TraitId(_) => fn_subst.at(Interner, 0).assert_ty_ref(Interner).clone(), + ItemContainerId::ImplId(impl_id) => { + fn_subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner) + } + // We should only get called for associated items (impl/trait) + ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { + unreachable!() + } + }; + check_that!(table.unify(&expect_self_ty, self_ty)); + + if let Some(receiver_ty) = receiver_ty { + check_that!(data.has_self_param()); + + let sig = db.callable_item_signature(fn_id.into()); + let expected_receiver = + sig.map(|s| s.params()[0].clone()).substitute(Interner, &fn_subst); + + check_that!(table.unify(&receiver_ty, &expected_receiver)); + } + + if let ItemContainerId::ImplId(impl_id) = container { + // We need to consider the bounds on the impl to distinguish functions of the same name + // for a type. + let predicates = db.generic_predicates(impl_id.into()); + predicates + .iter() + .map(|predicate| { + let (p, b) = predicate + .clone() + .substitute(Interner, &impl_subst) + // Skipping the inner binders is ok, as we don't handle quantified where + // clauses yet. + .into_value_and_skipped_binders(); + stdx::always!(b.len(Interner) == 0); + p + }) + // It's ok to get ambiguity here, as we may not have enough information to prove + // obligations. We'll check if the user is calling the selected method properly + // later anyway. + .all(|p| table.try_obligation(p.cast(Interner)).is_some()) + } else { + // For `ItemContainerId::TraitId`, we check if `self_ty` implements the trait in + // `iterate_trait_method_candidates()`. + // For others, this function shouldn't be called. + true + } + }) +} + pub fn implements_trait( ty: &Canonical<Ty>, db: &dyn HirDatabase, diff --git a/crates/hir-ty/src/test_db.rs b/crates/hir-ty/src/test_db.rs index dc7252f7072..118e5311e9a 100644 --- a/crates/hir-ty/src/test_db.rs +++ b/crates/hir-ty/src/test_db.rs @@ -10,7 +10,7 @@ use base_db::{ }; use hir_def::{db::DefDatabase, ModuleId}; use hir_expand::db::AstDatabase; -use rustc_hash::{FxHashMap, FxHashSet}; +use stdx::hash::{NoHashHashMap, NoHashHashSet}; use syntax::TextRange; use test_utils::extract_annotations; @@ -80,7 +80,7 @@ impl FileLoader for TestDB { fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { FileLoaderDelegate(self).resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { + fn relevant_crates(&self, file_id: FileId) -> Arc<NoHashHashSet<CrateId>> { FileLoaderDelegate(self).relevant_crates(file_id) } } @@ -102,7 +102,7 @@ impl TestDB { self.module_for_file_opt(file_id).unwrap() } - pub(crate) fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> { + pub(crate) fn extract_annotations(&self) -> NoHashHashMap<FileId, Vec<(TextRange, String)>> { let mut files = Vec::new(); let crate_graph = self.crate_graph(); for krate in crate_graph.iter() { diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index 68463dc068b..81588a7c4ff 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -1790,3 +1790,46 @@ impl u16 { "#, ) } + +#[test] +fn with_impl_bounds() { + check_types( + r#" +trait Trait {} +struct Foo<T>(T); +impl Trait for isize {} + +impl<T: Trait> Foo<T> { + fn foo() -> isize { 0 } + fn bar(&self) -> isize { 0 } +} + +impl Foo<()> { + fn foo() {} + fn bar(&self) {} +} + +fn f() { + let _ = Foo::<isize>::foo(); + //^isize + let _ = Foo(0isize).bar(); + //^isize + let _ = Foo::<()>::foo(); + //^() + let _ = Foo(()).bar(); + //^() + let _ = Foo::<usize>::foo(); + //^{unknown} + let _ = Foo(0usize).bar(); + //^{unknown} +} + +fn g<T: Trait>(a: T) { + let _ = Foo::<T>::foo(); + //^isize + let _ = Foo(a).bar(); + //^isize +} + "#, + ); +} diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index 94efe7bc11a..eb04bf87783 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -489,6 +489,42 @@ fn infer_adt_pattern() { } #[test] +fn tuple_struct_destructured_with_self() { + check_infer( + r#" +struct Foo(usize,); +impl Foo { + fn f() { + let Self(s,) = &Foo(0,); + let Self(s,) = &mut Foo(0,); + let Self(s,) = Foo(0,); + } +} + "#, + expect![[r#" + 42..151 '{ ... }': () + 56..64 'Self(s,)': Foo + 61..62 's': &usize + 67..75 '&Foo(0,)': &Foo + 68..71 'Foo': Foo(usize) -> Foo + 68..75 'Foo(0,)': Foo + 72..73 '0': usize + 89..97 'Self(s,)': Foo + 94..95 's': &mut usize + 100..112 '&mut Foo(0,)': &mut Foo + 105..108 'Foo': Foo(usize) -> Foo + 105..112 'Foo(0,)': Foo + 109..110 '0': usize + 126..134 'Self(s,)': Foo + 131..132 's': usize + 137..140 'Foo': Foo(usize) -> Foo + 137..144 'Foo(0,)': Foo + 141..142 '0': usize + "#]], + ); +} + +#[test] fn enum_variant_through_self_in_pattern() { check_infer( r#" diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 1b5ed0603bf..c7895db1afb 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -1527,6 +1527,34 @@ unsafe impl Storage for InlineStorage { } #[test] +fn gat_crash_3() { + // FIXME: This test currently crashes rust analyzer in a debug build but not in a + // release build (i.e. for the user). With the assumption that tests will always be run + // in debug mode, we catch the unwind and expect that it panicked. See the + // [`crate::utils::generics`] function for more information. + cov_mark::check!(ignore_gats); + std::panic::catch_unwind(|| { + check_no_mismatches( + r#" +trait Collection { + type Item; + type Member<T>: Collection<Item = T>; + fn add(&mut self, value: Self::Item) -> Result<(), Self::Error>; +} +struct ConstGen<T, const N: usize> { + data: [T; N], +} +impl<T, const N: usize> Collection for ConstGen<T, N> { + type Item = T; + type Member<U> = ConstGen<U, N>; +} + "#, + ); + }) + .expect_err("must panic"); +} + +#[test] fn cfgd_out_self_param() { cov_mark::check!(cfgd_out_self_param); check_no_mismatches( diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index 83319755da7..d6638db0285 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -176,10 +176,19 @@ pub(crate) fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics { let parent_generics = parent_generic_def(db, def).map(|def| Box::new(generics(db, def))); if parent_generics.is_some() && matches!(def, GenericDefId::TypeAliasId(_)) { let params = db.generic_params(def); + let parent_params = &parent_generics.as_ref().unwrap().params; let has_consts = params.iter().any(|(_, x)| matches!(x, TypeOrConstParamData::ConstParamData(_))); - return if has_consts { - // XXX: treat const generic associated types as not existing to avoid crashes (#11769) + let parent_has_consts = + parent_params.iter().any(|(_, x)| matches!(x, TypeOrConstParamData::ConstParamData(_))); + return if has_consts || parent_has_consts { + // XXX: treat const generic associated types as not existing to avoid crashes + // (#11769) + // + // Note: Also crashes when the parent has const generics (also even if the GAT + // doesn't use them), see `tests::regression::gat_crash_3` for an example. + // Avoids that by disabling GATs when the parent (i.e. `impl` block) has + // const generics (#12193). // // Chalk expects the inner associated type's parameters to come // *before*, not after the trait's generics as we've always done it. @@ -264,12 +273,8 @@ impl Generics { fn find_param(&self, param: TypeOrConstParamId) -> Option<(usize, &TypeOrConstParamData)> { if param.parent == self.def { - let (idx, (_local_id, data)) = self - .params - .iter() - .enumerate() - .find(|(_, (idx, _))| *idx == param.local_id) - .unwrap(); + let (idx, (_local_id, data)) = + self.params.iter().enumerate().find(|(_, (idx, _))| *idx == param.local_id)?; let parent_len = self.parent_generics().map_or(0, Generics::len); Some((parent_len + idx, data)) } else { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index aa019ca4838..6dccf2ed20b 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -511,6 +511,7 @@ impl Module { .collect() } + /// Fills `acc` with the module's diagnostics. pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) { let _p = profile::span("Module::diagnostics").detail(|| { format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) @@ -531,11 +532,21 @@ impl Module { m.diagnostics(db, acc) } } + ModuleDef::Trait(t) => { + for diag in db.trait_data_with_diagnostics(t.id).1.iter() { + emit_def_diagnostic(db, acc, diag); + } + acc.extend(decl.diagnostics(db)) + } _ => acc.extend(decl.diagnostics(db)), } } for impl_def in self.impl_defs(db) { + for diag in db.impl_data_with_diagnostics(impl_def.id).1.iter() { + emit_def_diagnostic(db, acc, diag); + } + for item in impl_def.items(db) { let def: DefWithBody = match item { AssocItem::Function(it) => it.into(), diff --git a/crates/ide-assists/src/handlers/extract_type_alias.rs b/crates/ide-assists/src/handlers/extract_type_alias.rs index af584cdb438..03aa8601d14 100644 --- a/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -171,6 +171,25 @@ fn collect_used_generics<'gp>( ast::Type::RefType(ref_) => generics.extend( ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), ), + ast::Type::ArrayType(ar) => { + if let Some(expr) = ar.expr() { + if let ast::Expr::PathExpr(p) = expr { + if let Some(path) = p.path() { + if let Some(name_ref) = path.as_single_name_ref() { + if let Some(param) = known_generics.iter().find(|gp| { + if let ast::GenericParam::ConstParam(cp) = gp { + cp.name().map_or(false, |n| n.text() == name_ref.text()) + } else { + false + } + }) { + generics.push(param); + } + } + } + } + } + } _ => (), }); // stable resort to lifetime, type, const @@ -357,4 +376,29 @@ impl<'outer, Outer, const OUTER: usize> () { "#, ); } + + #[test] + fn issue_11197() { + check_assist( + extract_type_alias, + r#" +struct Foo<T, const N: usize> +where + [T; N]: Sized, +{ + arr: $0[T; N]$0, +} + "#, + r#" +type $0Type<T, const N: usize> = [T; N]; + +struct Foo<T, const N: usize> +where + [T; N]: Sized, +{ + arr: Type<T, N>, +} + "#, + ); + } } diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index d564a054089..e26c76da189 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -61,56 +61,72 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { } let fn_name = &*name_ref.text(); - let target_module; - let mut adt_name = None; + let TargetInfo { target_module, adt_name, target, file, insert_offset } = + fn_target_info(ctx, path, &call, fn_name)?; + let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; + let text_range = call.syntax().text_range(); + let label = format!("Generate {} function", function_builder.fn_name); + add_func_to_accumulator( + acc, + ctx, + text_range, + function_builder, + insert_offset, + file, + adt_name, + label, + ) +} + +struct TargetInfo { + target_module: Option<Module>, + adt_name: Option<hir::Name>, + target: GeneratedFunctionTarget, + file: FileId, + insert_offset: TextSize, +} - let (target, file, insert_offset) = match path.qualifier() { +impl TargetInfo { + fn new( + target_module: Option<Module>, + adt_name: Option<hir::Name>, + target: GeneratedFunctionTarget, + file: FileId, + insert_offset: TextSize, + ) -> Self { + Self { target_module, adt_name, target, file, insert_offset } + } +} + +fn fn_target_info( + ctx: &AssistContext<'_>, + path: ast::Path, + call: &CallExpr, + fn_name: &str, +) -> Option<TargetInfo> { + match path.qualifier() { Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => { - target_module = Some(module); - get_fn_target(ctx, &target_module, call.clone())? + get_fn_target_info(ctx, &Some(module), call.clone()) } Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) => { if let hir::Adt::Enum(_) = adt { // Don't suggest generating function if the name starts with an uppercase letter - if name_ref.text().starts_with(char::is_uppercase) { + if fn_name.starts_with(char::is_uppercase) { return None; } } - let current_module = ctx.sema.scope(call.syntax())?.module(); - let module = adt.module(ctx.sema.db); - target_module = if current_module == module { None } else { Some(module) }; - if current_module.krate() != module.krate() { - return None; - } - let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?; - let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?; - adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; - (target, file, insert_offset) + assoc_fn_target_info(ctx, call, adt, fn_name) } - _ => { - return None; + Some(hir::PathResolution::SelfType(impl_)) => { + let adt = impl_.self_ty(ctx.db()).as_adt()?; + assoc_fn_target_info(ctx, call, adt, fn_name) } + _ => None, }, - _ => { - target_module = None; - get_fn_target(ctx, &target_module, call.clone())? - } - }; - let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; - let text_range = call.syntax().text_range(); - let label = format!("Generate {} function", function_builder.fn_name); - add_func_to_accumulator( - acc, - ctx, - text_range, - function_builder, - insert_offset, - file, - adt_name, - label, - ) + _ => get_fn_target_info(ctx, &None, call.clone()), + } } fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { @@ -366,6 +382,15 @@ fn make_return_type( (ret_type, should_focus_return_type) } +fn get_fn_target_info( + ctx: &AssistContext<'_>, + target_module: &Option<Module>, + call: CallExpr, +) -> Option<TargetInfo> { + let (target, file, insert_offset) = get_fn_target(ctx, target_module, call)?; + Some(TargetInfo::new(*target_module, None, target, file, insert_offset)) +} + fn get_fn_target( ctx: &AssistContext<'_>, target_module: &Option<Module>, @@ -399,6 +424,24 @@ fn get_method_target( Some((target.clone(), get_insert_offset(&target))) } +fn assoc_fn_target_info( + ctx: &AssistContext<'_>, + call: &CallExpr, + adt: hir::Adt, + fn_name: &str, +) -> Option<TargetInfo> { + let current_module = ctx.sema.scope(call.syntax())?.module(); + let module = adt.module(ctx.sema.db); + let target_module = if current_module == module { None } else { Some(module) }; + if current_module.krate() != module.krate() { + return None; + } + let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?; + let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?; + let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; + Some(TargetInfo::new(target_module, adt_name, target, file, insert_offset)) +} + fn get_insert_offset(target: &GeneratedFunctionTarget) -> TextSize { match &target { GeneratedFunctionTarget::BehindItem(it) => it.text_range().end(), @@ -1634,6 +1677,33 @@ fn bar() ${0:-> _} { } #[test] + fn create_static_method_within_an_impl_with_self_syntax() { + check_assist( + generate_function, + r" +struct S; +impl S { + fn foo(&self) { + Self::bar$0(); + } +} +", + r" +struct S; +impl S { + fn foo(&self) { + Self::bar(); + } + + fn bar() ${0:-> _} { + todo!() + } +} +", + ) + } + + #[test] fn no_panic_on_invalid_global_path() { check_assist( generate_function, diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 80d3b925593..96890ad51a6 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -13,7 +13,7 @@ use ide_db::{ use itertools::{izip, Itertools}; use syntax::{ ast::{self, edit_in_place::Indent, HasArgList, PathExpr}, - ted, AstNode, + ted, AstNode, NodeOrToken, SyntaxKind, }; use crate::{ @@ -311,6 +311,17 @@ fn inline( } else { fn_body.clone_for_update() }; + if let Some(imp) = body.syntax().ancestors().find_map(ast::Impl::cast) { + if !node.syntax().ancestors().any(|anc| &anc == imp.syntax()) { + if let Some(t) = imp.self_ty() { + body.syntax() + .descendants_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW) + .for_each(|tok| ted::replace(tok, t.syntax())); + } + } + } let usages_for_locals = |local| { Definition::Local(local) .usages(sema) @@ -345,6 +356,7 @@ fn inline( } }) .collect(); + if function.self_param(sema.db).is_some() { let this = || make::name_ref("this").syntax().clone_for_update(); if let Some(self_local) = params[0].2.as_local(sema.db) { @@ -1191,4 +1203,54 @@ fn bar() -> u32 { "#, ) } + + #[test] + fn inline_call_with_self_type() { + check_assist( + inline_call, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(114514) } +} +fn main() { + A::f$0(); +} +"#, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(114514) } +} +fn main() { + A(114514); +} +"#, + ) + } + + #[test] + fn inline_call_with_self_type_but_within_same_impl() { + check_assist( + inline_call, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(1919810) } + fn main() { + Self::f$0(); + } +} +"#, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(1919810) } + fn main() { + Self(1919810); + } +} +"#, + ) + } } diff --git a/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs b/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs index 5242f3b5100..521447c26df 100644 --- a/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs +++ b/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs @@ -1,5 +1,6 @@ +use hir::HirDisplay; use syntax::{ - ast::{Expr, GenericArg}, + ast::{Expr, GenericArg, GenericArgList}, ast::{LetStmt, Type::InferType}, AstNode, TextRange, }; @@ -34,21 +35,7 @@ pub(crate) fn replace_turbofish_with_explicit_type( let initializer = let_stmt.initializer()?; - let generic_args = match &initializer { - Expr::MethodCallExpr(ce) => ce.generic_arg_list()?, - Expr::CallExpr(ce) => { - if let Expr::PathExpr(pe) = ce.expr()? { - pe.path()?.segment()?.generic_arg_list()? - } else { - cov_mark::hit!(not_applicable_if_non_path_function_call); - return None; - } - } - _ => { - cov_mark::hit!(not_applicable_if_non_function_call_initializer); - return None; - } - }; + let generic_args = generic_arg_list(&initializer)?; // Find range of ::<_> let colon2 = generic_args.coloncolon_token()?; @@ -65,7 +52,16 @@ pub(crate) fn replace_turbofish_with_explicit_type( // An improvement would be to check that this is correctly part of the return value of the // function call, or sub in the actual return type. - let turbofish_type = &turbofish_args[0]; + let returned_type = match ctx.sema.type_of_expr(&initializer) { + Some(returned_type) if !returned_type.original.contains_unknown() => { + let module = ctx.sema.scope(let_stmt.syntax())?.module(); + returned_type.original.display_source_code(ctx.db(), module.into()).ok()? + } + _ => { + cov_mark::hit!(fallback_to_turbofish_type_if_type_info_not_available); + turbofish_args[0].to_string() + } + }; let initializer_start = initializer.syntax().text_range().start(); if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start { @@ -83,7 +79,7 @@ pub(crate) fn replace_turbofish_with_explicit_type( "Replace turbofish with explicit type", TextRange::new(initializer_start, turbofish_range.end()), |builder| { - builder.insert(ident_range.end(), format!(": {}", turbofish_type)); + builder.insert(ident_range.end(), format!(": {}", returned_type)); builder.delete(turbofish_range); }, ); @@ -98,7 +94,7 @@ pub(crate) fn replace_turbofish_with_explicit_type( "Replace `_` with turbofish type", turbofish_range, |builder| { - builder.replace(underscore_range, turbofish_type.to_string()); + builder.replace(underscore_range, returned_type); builder.delete(turbofish_range); }, ); @@ -107,6 +103,26 @@ pub(crate) fn replace_turbofish_with_explicit_type( None } +fn generic_arg_list(expr: &Expr) -> Option<GenericArgList> { + match expr { + Expr::MethodCallExpr(expr) => expr.generic_arg_list(), + Expr::CallExpr(expr) => { + if let Expr::PathExpr(pe) = expr.expr()? { + pe.path()?.segment()?.generic_arg_list() + } else { + cov_mark::hit!(not_applicable_if_non_path_function_call); + return None; + } + } + Expr::AwaitExpr(expr) => generic_arg_list(&expr.expr()?), + Expr::TryExpr(expr) => generic_arg_list(&expr.expr()?), + _ => { + cov_mark::hit!(not_applicable_if_non_function_call_initializer); + None + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -115,6 +131,7 @@ mod tests { #[test] fn replaces_turbofish_for_vec_string() { + cov_mark::check!(fallback_to_turbofish_type_if_type_info_not_available); check_assist( replace_turbofish_with_explicit_type, r#" @@ -135,6 +152,7 @@ fn main() { #[test] fn replaces_method_calls() { // foo.make() is a method call which uses a different expr in the let initializer + cov_mark::check!(fallback_to_turbofish_type_if_type_info_not_available); check_assist( replace_turbofish_with_explicit_type, r#" @@ -240,4 +258,108 @@ fn main() { "#, ); } + + #[test] + fn replaces_turbofish_for_known_type() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +fn make<T>() -> T {} +fn main() { + let a = make$0::<i32>(); +} +"#, + r#" +fn make<T>() -> T {} +fn main() { + let a: i32 = make(); +} +"#, + ); + check_assist( + replace_turbofish_with_explicit_type, + r#" +//- minicore: option +fn make<T>() -> T {} +fn main() { + let a = make$0::<Option<bool>>(); +} +"#, + r#" +fn make<T>() -> T {} +fn main() { + let a: Option<bool> = make(); +} +"#, + ); + } + + #[test] + fn replaces_turbofish_not_same_type() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +//- minicore: option +fn make<T>() -> Option<T> {} +fn main() { + let a = make$0::<u128>(); +} +"#, + r#" +fn make<T>() -> Option<T> {} +fn main() { + let a: Option<u128> = make(); +} +"#, + ); + } + + #[test] + fn replaces_turbofish_for_type_with_defaulted_generic_param() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +struct HasDefault<T, U = i32>(T, U); +fn make<T>() -> HasDefault<T> {} +fn main() { + let a = make$0::<bool>(); +} +"#, + r#" +struct HasDefault<T, U = i32>(T, U); +fn make<T>() -> HasDefault<T> {} +fn main() { + let a: HasDefault<bool> = make(); +} +"#, + ); + } + + #[test] + fn replaces_turbofish_try_await() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +//- minicore: option, future +struct Fut<T>(T); +impl<T> core::future::Future for Fut<T> { + type Output = Option<T>; +} +fn make<T>() -> Fut<T> {} +fn main() { + let a = make$0::<bool>().await?; +} +"#, + r#" +struct Fut<T>(T); +impl<T> core::future::Future for Fut<T> { + type Output = Option<T>; +} +fn make<T>() -> Fut<T> {} +fn main() { + let a: bool = make().await?; +} +"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/unmerge_match_arm.rs b/crates/ide-assists/src/handlers/unmerge_match_arm.rs new file mode 100644 index 00000000000..9565f0ee6f2 --- /dev/null +++ b/crates/ide-assists/src/handlers/unmerge_match_arm.rs @@ -0,0 +1,293 @@ +use syntax::{ + algo::neighbor, + ast::{self, edit::IndentLevel, make, AstNode}, + ted::{self, Position}, + Direction, SyntaxKind, T, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: unmerge_match_arm +// +// Splits the current match with a `|` pattern into two arms with identical bodies. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move(..) $0| Action::Stop => foo(), +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move(..) => foo(), +// Action::Stop => foo(), +// } +// } +// ``` +pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let pipe_token = ctx.find_token_syntax_at_offset(T![|])?; + let or_pat = ast::OrPat::cast(pipe_token.parent()?)?.clone_for_update(); + let match_arm = ast::MatchArm::cast(or_pat.syntax().parent()?)?; + let match_arm_body = match_arm.expr()?; + + // We don't need to check for leading pipe because it is directly under `MatchArm` + // without `OrPat`. + + let new_parent = match_arm.syntax().parent()?; + let old_parent_range = new_parent.text_range(); + + acc.add( + AssistId("unmerge_match_arm", AssistKind::RefactorRewrite), + "Unmerge match arm", + pipe_token.text_range(), + |edit| { + let pats_after = pipe_token + .siblings_with_tokens(Direction::Next) + .filter_map(|it| ast::Pat::cast(it.into_node()?)); + // FIXME: We should add a leading pipe if the original arm has one. + let new_match_arm = make::match_arm( + pats_after, + match_arm.guard().and_then(|guard| guard.condition()), + match_arm_body, + ) + .clone_for_update(); + + let mut pipe_index = pipe_token.index(); + if pipe_token + .prev_sibling_or_token() + .map_or(false, |it| it.kind() == SyntaxKind::WHITESPACE) + { + pipe_index -= 1; + } + or_pat.syntax().splice_children( + pipe_index..or_pat.syntax().children_with_tokens().count(), + Vec::new(), + ); + + let mut insert_after_old_arm = Vec::new(); + + // A comma can be: + // - After the arm. In this case we always want to insert a comma after the newly + // inserted arm. + // - Missing after the arm, with no arms after. In this case we want to insert a + // comma before the newly inserted arm. It can not be necessary if there arm + // body is a block, but we don't bother to check that. + // - Missing after the arm with arms after, if the arm body is a block. In this case + // we don't want to insert a comma at all. + let has_comma_after = + std::iter::successors(match_arm.syntax().last_child_or_token(), |it| { + it.prev_sibling_or_token() + }) + .map(|it| it.kind()) + .skip_while(|it| it.is_trivia()) + .next() + == Some(T![,]); + let has_arms_after = neighbor(&match_arm, Direction::Next).is_some(); + if !has_comma_after && !has_arms_after { + insert_after_old_arm.push(make::token(T![,]).into()); + } + + let indent = IndentLevel::from_node(match_arm.syntax()); + insert_after_old_arm.push(make::tokens::whitespace(&format!("\n{indent}")).into()); + + insert_after_old_arm.push(new_match_arm.syntax().clone().into()); + + ted::insert_all_raw(Position::after(match_arm.syntax()), insert_after_old_arm); + + if has_comma_after { + ted::insert_raw( + Position::last_child_of(new_match_arm.syntax()), + make::token(T![,]), + ); + } + + edit.replace(old_parent_range, new_parent.to_string()); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn unmerge_match_arm_single_pipe() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A $0| X::B => { 1i32 } + X::C => { 2i32 } + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32 } + X::B => { 1i32 } + X::C => { 2i32 } + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_guard() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A $0| X::B if true => { 1i32 } + _ => { 2i32 } + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A if true => { 1i32 } + X::B if true => { 1i32 } + _ => { 2i32 } + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_leading_pipe() { + check_assist_not_applicable( + unmerge_match_arm, + r#" + +fn main() { + let y = match 0 { + |$0 0 => { 1i32 } + 1 => { 2i32 } + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_multiple_pipes() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B, C, D, E } + +fn main() { + let x = X::A; + let y = match x { + X::A | X::B |$0 X::C | X::D => 1i32, + X::E => 2i32, + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B, C, D, E } + +fn main() { + let x = X::A; + let y = match x { + X::A | X::B => 1i32, + X::C | X::D => 1i32, + X::E => 2i32, + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_inserts_comma_if_required() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + let y = match x { + X::A $0| X::B => 1i32 + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + let y = match x { + X::A => 1i32, + X::B => 1i32 + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_inserts_comma_if_had_after() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + match x { + X::A $0| X::B => {}, + } +} +"#, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + match x { + X::A => {}, + X::B => {}, + } +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 7fb35143fa2..c558553b1cb 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -185,6 +185,7 @@ mod handlers { mod replace_string_with_char; mod replace_turbofish_with_explicit_type; mod split_import; + mod unmerge_match_arm; mod sort_items; mod toggle_ignore; mod unmerge_use; @@ -278,6 +279,7 @@ mod handlers { sort_items::sort_items, split_import::split_import, toggle_ignore::toggle_ignore, + unmerge_match_arm::unmerge_match_arm, unmerge_use::unmerge_use, unnecessary_async::unnecessary_async, unwrap_block::unwrap_block, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 22319f36134..7b2c16806b2 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2208,6 +2208,32 @@ fn arithmetics { } #[test] +fn doctest_unmerge_match_arm() { + check_doc_test( + "unmerge_match_arm", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move(..) $0| Action::Stop => foo(), + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move(..) => foo(), + Action::Stop => foo(), + } +} +"#####, + ) +} + +#[test] fn doctest_unmerge_use() { check_doc_test( "unmerge_use", diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 759742d3472..a5e854b74df 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -64,8 +64,11 @@ pub(crate) struct PathCompletionCtx { pub(super) qualified: Qualified, /// The parent of the path we are completing. pub(super) parent: Option<ast::Path>, + #[allow(dead_code)] /// The path of which we are completing the segment pub(super) path: ast::Path, + /// The path of which we are completing the segment in the original file + pub(crate) original_path: Option<ast::Path>, pub(super) kind: PathKind, /// Whether the path segment has type args or not. pub(super) has_type_args: bool, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 22ec7cead49..01dd9a234f5 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -588,12 +588,15 @@ impl<'a> CompletionContext<'a> { }; let path = segment.parent_path(); + let original_path = find_node_in_file_compensated(sema, original_file, &path); + let mut path_ctx = PathCompletionCtx { has_call_parens: false, has_macro_bang: false, qualified: Qualified::No, parent: None, path: path.clone(), + original_path, kind: PathKind::Item { kind: ItemListKind::SourceFile }, has_type_args: false, use_tree_parent: false, diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index a2cf6d68e5b..86302cb0678 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -323,9 +323,7 @@ fn render_resolution_path( ..CompletionRelevance::default() }); - if let Some(ref_match) = compute_ref_match(completion, &ty) { - item.ref_match(ref_match, path_ctx.path.syntax().text_range().start()); - } + path_ref_match(completion, path_ctx, &ty, &mut item); }; item } @@ -453,6 +451,29 @@ fn compute_ref_match( None } +fn path_ref_match( + completion: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx, + ty: &hir::Type, + item: &mut Builder, +) { + if let Some(original_path) = &path_ctx.original_path { + // At least one char was typed by the user already, in that case look for the original path + if let Some(original_path) = completion.sema.original_ast_node(original_path.clone()) { + if let Some(ref_match) = compute_ref_match(completion, ty) { + item.ref_match(ref_match, original_path.syntax().text_range().start()); + } + } + } else { + // completion requested on an empty identifier, there is no path here yet. + // FIXME: This might create inconsistent completions where we show a ref match in macro inputs + // as long as nothing was typed yet + if let Some(ref_match) = compute_ref_match(completion, ty) { + item.ref_match(ref_match, completion.position.offset); + } + } +} + #[cfg(test)] mod tests { use std::cmp; diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 9cf64691298..37612084604 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -79,18 +79,18 @@ fn render( ..ctx.completion_relevance() }); - if let Some(ref_match) = compute_ref_match(completion, &ret_type) { - match func_kind { - FuncKind::Function(path_ctx) => { - item.ref_match(ref_match, path_ctx.path.syntax().text_range().start()); - } - FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => { - if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) { + match func_kind { + FuncKind::Function(path_ctx) => { + super::path_ref_match(completion, path_ctx, &ret_type, &mut item); + } + FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => { + if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) { + if let Some(ref_match) = compute_ref_match(completion, &ret_type) { item.ref_match(ref_match, original_expr.syntax().text_range().start()); } } - _ => (), } + _ => (), } item.set_documentation(ctx.docs(func)) diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs index af9c88a7e0a..0c791ac570c 100644 --- a/crates/ide-completion/src/render/literal.rs +++ b/crates/ide-completion/src/render/literal.rs @@ -2,13 +2,12 @@ use hir::{db::HirDatabase, Documentation, HasAttrs, StructKind}; use ide_db::SymbolKind; -use syntax::AstNode; use crate::{ context::{CompletionContext, PathCompletionCtx, PathKind}, item::{Builder, CompletionItem}, render::{ - compute_ref_match, compute_type_match, + compute_type_match, variant::{ format_literal_label, format_literal_lookup, render_record_lit, render_tuple_lit, visible_fields, RenderedLiteral, @@ -125,9 +124,8 @@ fn render( type_match: compute_type_match(ctx.completion, &ty), ..ctx.completion_relevance() }); - if let Some(ref_match) = compute_ref_match(completion, &ty) { - item.ref_match(ref_match, path_ctx.path.syntax().text_range().start()); - } + + super::path_ref_match(completion, path_ctx, &ty, &mut item); if let Some(import_to_add) = ctx.import_to_add { item.add_import(import_to_add); diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs index 966bba616f6..1ec62a8425a 100644 --- a/crates/ide-db/src/lib.rs +++ b/crates/ide-db/src/lib.rs @@ -52,6 +52,7 @@ use hir::{ db::{AstDatabase, DefDatabase, HirDatabase}, symbols::FileSymbolKind, }; +use stdx::hash::NoHashHashSet; use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase}; pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; @@ -118,7 +119,7 @@ impl FileLoader for RootDatabase { fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { FileLoaderDelegate(self).resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { + fn relevant_crates(&self, file_id: FileId) -> Arc<NoHashHashSet<CrateId>> { FileLoaderDelegate(self).relevant_crates(file_id) } } diff --git a/crates/ide-db/src/line_index.rs b/crates/ide-db/src/line_index.rs index 68ad07ee83f..75d49ff2fd7 100644 --- a/crates/ide-db/src/line_index.rs +++ b/crates/ide-db/src/line_index.rs @@ -2,7 +2,7 @@ //! representation. use std::{iter, mem}; -use rustc_hash::FxHashMap; +use stdx::hash::NoHashHashMap; use syntax::{TextRange, TextSize}; #[derive(Clone, Debug, PartialEq, Eq)] @@ -10,7 +10,7 @@ pub struct LineIndex { /// Offset the the beginning of each line, zero-based pub(crate) newlines: Vec<TextSize>, /// List of non-ASCII characters on each line - pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>, + pub(crate) utf16_lines: NoHashHashMap<u32, Vec<Utf16Char>>, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -55,7 +55,7 @@ impl Utf16Char { impl LineIndex { pub fn new(text: &str) -> LineIndex { - let mut utf16_lines = FxHashMap::default(); + let mut utf16_lines = NoHashHashMap::default(); let mut utf16_chars = Vec::new(); let mut newlines = vec![0.into()]; diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index 2f4aa113170..7deffe8e0f6 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -9,7 +9,7 @@ use std::{mem, sync::Arc}; use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility}; use once_cell::unsync::Lazy; -use rustc_hash::FxHashMap; +use stdx::hash::NoHashHashMap; use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; use crate::{ @@ -20,7 +20,7 @@ use crate::{ #[derive(Debug, Default, Clone)] pub struct UsageSearchResult { - pub references: FxHashMap<FileId, Vec<FileReference>>, + pub references: NoHashHashMap<FileId, Vec<FileReference>>, } impl UsageSearchResult { @@ -45,7 +45,7 @@ impl UsageSearchResult { impl IntoIterator for UsageSearchResult { type Item = (FileId, Vec<FileReference>); - type IntoIter = <FxHashMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter; + type IntoIter = <NoHashHashMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.references.into_iter() @@ -78,17 +78,17 @@ pub enum ReferenceCategory { /// e.g. for things like local variables. #[derive(Clone, Debug)] pub struct SearchScope { - entries: FxHashMap<FileId, Option<TextRange>>, + entries: NoHashHashMap<FileId, Option<TextRange>>, } impl SearchScope { - fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope { + fn new(entries: NoHashHashMap<FileId, Option<TextRange>>) -> SearchScope { SearchScope { entries } } /// Build a search scope spanning the entire crate graph of files. fn crate_graph(db: &RootDatabase) -> SearchScope { - let mut entries = FxHashMap::default(); + let mut entries = NoHashHashMap::default(); let graph = db.crate_graph(); for krate in graph.iter() { @@ -102,7 +102,7 @@ impl SearchScope { /// Build a search scope spanning all the reverse dependencies of the given crate. fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope { - let mut entries = FxHashMap::default(); + let mut entries = NoHashHashMap::default(); for rev_dep in of.transitive_reverse_dependencies(db) { let root_file = rev_dep.root_file(db); let source_root_id = db.file_source_root(root_file); @@ -117,14 +117,12 @@ impl SearchScope { let root_file = of.root_file(db); let source_root_id = db.file_source_root(root_file); let source_root = db.source_root(source_root_id); - SearchScope { - entries: source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>(), - } + SearchScope { entries: source_root.iter().map(|id| (id, None)).collect() } } /// Build a search scope spanning the given module and all its submodules. fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope { - let mut entries = FxHashMap::default(); + let mut entries = NoHashHashMap::default(); let (file_id, range) = { let InFile { file_id, value } = module.definition_source(db); @@ -157,7 +155,7 @@ impl SearchScope { /// Build an empty search scope. pub fn empty() -> SearchScope { - SearchScope::new(FxHashMap::default()) + SearchScope::new(NoHashHashMap::default()) } /// Build a empty search scope spanning the given file. diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index 21314ad74ef..8e338061df4 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -6,8 +6,7 @@ use std::{collections::hash_map::Entry, iter, mem}; use base_db::{AnchoredPathBuf, FileId}; -use rustc_hash::FxHashMap; -use stdx::never; +use stdx::{hash::NoHashHashMap, never}; use syntax::{algo, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize}; use text_edit::{TextEdit, TextEditBuilder}; @@ -15,7 +14,7 @@ use crate::SnippetCap; #[derive(Default, Debug, Clone)] pub struct SourceChange { - pub source_file_edits: FxHashMap<FileId, TextEdit>, + pub source_file_edits: NoHashHashMap<FileId, TextEdit>, pub file_system_edits: Vec<FileSystemEdit>, pub is_snippet: bool, } @@ -24,7 +23,7 @@ impl SourceChange { /// Creates a new SourceChange with the given label /// from the edits. pub fn from_edits( - source_file_edits: FxHashMap<FileId, TextEdit>, + source_file_edits: NoHashHashMap<FileId, TextEdit>, file_system_edits: Vec<FileSystemEdit>, ) -> Self { SourceChange { source_file_edits, file_system_edits, is_snippet: false } @@ -78,8 +77,8 @@ impl Extend<FileSystemEdit> for SourceChange { } } -impl From<FxHashMap<FileId, TextEdit>> for SourceChange { - fn from(source_file_edits: FxHashMap<FileId, TextEdit>) -> SourceChange { +impl From<NoHashHashMap<FileId, TextEdit>> for SourceChange { + fn from(source_file_edits: NoHashHashMap<FileId, TextEdit>) -> SourceChange { SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } } } diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs index 5694f33525e..04918891b25 100644 --- a/crates/ide-diagnostics/src/handlers/inactive_code.rs +++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs @@ -106,18 +106,17 @@ fn f() { #[test] fn inactive_assoc_item() { - // FIXME these currently don't work, hence the * check( r#" struct Foo; impl Foo { #[cfg(any())] pub fn f() {} - //*************************** weak: code is inactive due to #[cfg] directives + //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives } trait Bar { #[cfg(any())] pub fn f() {} - //*************************** weak: code is inactive due to #[cfg] directives + //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives } "#, ); diff --git a/crates/ide-ssr/Cargo.toml b/crates/ide-ssr/Cargo.toml index d36dd02d45c..73314e0f330 100644 --- a/crates/ide-ssr/Cargo.toml +++ b/crates/ide-ssr/Cargo.toml @@ -20,6 +20,7 @@ parser = { path = "../parser", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } ide-db = { path = "../ide-db", version = "0.0.0" } hir = { path = "../hir", version = "0.0.0" } +stdx = { path = "../stdx", version = "0.0.0" } [dev-dependencies] test-utils = { path = "../test-utils" } diff --git a/crates/ide-ssr/src/lib.rs b/crates/ide-ssr/src/lib.rs index 739e0ccb436..d9834ee63ad 100644 --- a/crates/ide-ssr/src/lib.rs +++ b/crates/ide-ssr/src/lib.rs @@ -86,11 +86,9 @@ pub use crate::{errors::SsrError, from_comment::ssr_from_comment, matching::Matc use crate::{errors::bail, matching::MatchFailureReason}; use hir::Semantics; -use ide_db::{ - base_db::{FileId, FilePosition, FileRange}, - FxHashMap, -}; +use ide_db::base_db::{FileId, FilePosition, FileRange}; use resolving::ResolvedRule; +use stdx::hash::NoHashHashMap; use syntax::{ast, AstNode, SyntaxNode, TextRange}; use text_edit::TextEdit; @@ -170,9 +168,9 @@ impl<'db> MatchFinder<'db> { } /// Finds matches for all added rules and returns edits for all found matches. - pub fn edits(&self) -> FxHashMap<FileId, TextEdit> { + pub fn edits(&self) -> NoHashHashMap<FileId, TextEdit> { use ide_db::base_db::SourceDatabaseExt; - let mut matches_by_file = FxHashMap::default(); + let mut matches_by_file = NoHashHashMap::default(); for m in self.matches().matches { matches_by_file .entry(m.range.file_id) diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 582e9fe7e80..92ce26b422e 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -184,10 +184,10 @@ pub(crate) fn resolve_doc_path_for_def( Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns), Definition::Macro(it) => it.resolve_doc_path(db, link, ns), Definition::Field(it) => it.resolve_doc_path(db, link, ns), + Definition::SelfType(it) => it.resolve_doc_path(db, link, ns), Definition::BuiltinAttr(_) | Definition::ToolModule(_) | Definition::BuiltinType(_) - | Definition::SelfType(_) | Definition::Local(_) | Definition::GenericParam(_) | Definition::Label(_) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index dd108fa7999..d61d69a090b 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -87,7 +87,7 @@ pub use crate::{ }, join_lines::JoinLinesConfig, markup::Markup, - moniker::{MonikerKind, MonikerResult, PackageInformation}, + moniker::{MonikerDescriptorKind, MonikerKind, MonikerResult, PackageInformation}, move_item::Direction, navigation_target::NavigationTarget, prime_caches::ParallelPrimeCachesProgress, @@ -98,7 +98,7 @@ pub use crate::{ static_index::{StaticIndex, StaticIndexedFile, TokenId, TokenStaticData}, syntax_highlighting::{ tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, - HlRange, + HighlightConfig, HlRange, }, }; pub use hir::{Documentation, Semantics}; @@ -517,8 +517,12 @@ impl Analysis { } /// Computes syntax highlighting for the given file - pub fn highlight(&self, file_id: FileId) -> Cancellable<Vec<HlRange>> { - self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) + pub fn highlight( + &self, + highlight_config: HighlightConfig, + file_id: FileId, + ) -> Cancellable<Vec<HlRange>> { + self.with_db(|db| syntax_highlighting::highlight(db, highlight_config, file_id, None)) } /// Computes all ranges to highlight for a given item in a file. @@ -533,9 +537,13 @@ impl Analysis { } /// Computes syntax highlighting for the given file range. - pub fn highlight_range(&self, frange: FileRange) -> Cancellable<Vec<HlRange>> { + pub fn highlight_range( + &self, + highlight_config: HighlightConfig, + frange: FileRange, + ) -> Cancellable<Vec<HlRange>> { self.with_db(|db| { - syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false) + syntax_highlighting::highlight(db, highlight_config, frange.file_id, Some(frange.range)) }) } diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index 4f758967b46..600a526300c 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -14,16 +14,38 @@ use syntax::{AstNode, SyntaxKind::*, T}; use crate::{doc_links::token_as_doc_comment, RangeInfo}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MonikerDescriptorKind { + Namespace, + Type, + Term, + Method, + TypeParameter, + Parameter, + Macro, + Meta, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MonikerDescriptor { + pub name: Name, + pub desc: MonikerDescriptorKind, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MonikerIdentifier { - crate_name: String, - path: Vec<Name>, + pub crate_name: String, + pub description: Vec<MonikerDescriptor>, } impl ToString for MonikerIdentifier { fn to_string(&self) -> String { match self { - MonikerIdentifier { path, crate_name } => { - format!("{}::{}", crate_name, path.iter().map(|x| x.to_string()).join("::")) + MonikerIdentifier { description, crate_name } => { + format!( + "{}::{}", + crate_name, + description.iter().map(|x| x.name.to_string()).join("::") + ) } } } @@ -42,6 +64,12 @@ pub struct MonikerResult { pub package_information: PackageInformation, } +impl MonikerResult { + pub fn from_def(db: &RootDatabase, def: Definition, from_crate: Crate) -> Option<Self> { + def_to_moniker(db, def, from_crate) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PackageInformation { pub name: String, @@ -105,13 +133,23 @@ pub(crate) fn def_to_moniker( def: Definition, from_crate: Crate, ) -> Option<MonikerResult> { - if matches!(def, Definition::GenericParam(_) | Definition::SelfType(_) | Definition::Local(_)) { + if matches!( + def, + Definition::GenericParam(_) + | Definition::Label(_) + | Definition::DeriveHelper(_) + | Definition::BuiltinAttr(_) + | Definition::ToolModule(_) + ) { return None; } + let module = def.module(db)?; let krate = module.krate(); - let mut path = vec![]; - path.extend(module.path_to_root(db).into_iter().filter_map(|x| x.name(db))); + let mut description = vec![]; + description.extend(module.path_to_root(db).into_iter().filter_map(|x| { + Some(MonikerDescriptor { name: x.name(db)?, desc: MonikerDescriptorKind::Namespace }) + })); // Handle associated items within a trait if let Some(assoc) = def.as_assoc_item(db) { @@ -120,31 +158,98 @@ pub(crate) fn def_to_moniker( AssocItemContainer::Trait(trait_) => { // Because different traits can have functions with the same name, // we have to include the trait name as part of the moniker for uniqueness. - path.push(trait_.name(db)); + description.push(MonikerDescriptor { + name: trait_.name(db), + desc: MonikerDescriptorKind::Type, + }); } AssocItemContainer::Impl(impl_) => { // Because a struct can implement multiple traits, for implementations // we add both the struct name and the trait name to the path if let Some(adt) = impl_.self_ty(db).as_adt() { - path.push(adt.name(db)); + description.push(MonikerDescriptor { + name: adt.name(db), + desc: MonikerDescriptorKind::Type, + }); } if let Some(trait_) = impl_.trait_(db) { - path.push(trait_.name(db)); + description.push(MonikerDescriptor { + name: trait_.name(db), + desc: MonikerDescriptorKind::Type, + }); } } } } if let Definition::Field(it) = def { - path.push(it.parent_def(db).name(db)); + description.push(MonikerDescriptor { + name: it.parent_def(db).name(db), + desc: MonikerDescriptorKind::Type, + }); } - path.push(def.name(db)?); + let name_desc = match def { + // These are handled by top-level guard (for performance). + Definition::GenericParam(_) + | Definition::Label(_) + | Definition::DeriveHelper(_) + | Definition::BuiltinAttr(_) + | Definition::ToolModule(_) => return None, + + Definition::Local(local) => { + if !local.is_param(db) { + return None; + } + + MonikerDescriptor { name: local.name(db), desc: MonikerDescriptorKind::Parameter } + } + Definition::Macro(m) => { + MonikerDescriptor { name: m.name(db), desc: MonikerDescriptorKind::Macro } + } + Definition::Function(f) => { + MonikerDescriptor { name: f.name(db), desc: MonikerDescriptorKind::Method } + } + Definition::Variant(v) => { + MonikerDescriptor { name: v.name(db), desc: MonikerDescriptorKind::Type } + } + Definition::Const(c) => { + MonikerDescriptor { name: c.name(db)?, desc: MonikerDescriptorKind::Term } + } + Definition::Trait(trait_) => { + MonikerDescriptor { name: trait_.name(db), desc: MonikerDescriptorKind::Type } + } + Definition::TypeAlias(ta) => { + MonikerDescriptor { name: ta.name(db), desc: MonikerDescriptorKind::TypeParameter } + } + Definition::Module(m) => { + MonikerDescriptor { name: m.name(db)?, desc: MonikerDescriptorKind::Namespace } + } + Definition::BuiltinType(b) => { + MonikerDescriptor { name: b.name(), desc: MonikerDescriptorKind::Type } + } + Definition::SelfType(imp) => MonikerDescriptor { + name: imp.self_ty(db).as_adt()?.name(db), + desc: MonikerDescriptorKind::Type, + }, + Definition::Field(it) => { + MonikerDescriptor { name: it.name(db), desc: MonikerDescriptorKind::Term } + } + Definition::Adt(adt) => { + MonikerDescriptor { name: adt.name(db), desc: MonikerDescriptorKind::Type } + } + Definition::Static(s) => { + MonikerDescriptor { name: s.name(db), desc: MonikerDescriptorKind::Meta } + } + }; + + description.push(name_desc); + Some(MonikerResult { identifier: MonikerIdentifier { crate_name: krate.display_name(db)?.crate_name().to_string(), - path, + description, }, kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import }, package_information: { diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs index 29627003600..87b3ef380c5 100644 --- a/crates/ide/src/prime_caches.rs +++ b/crates/ide/src/prime_caches.rs @@ -12,8 +12,9 @@ use ide_db::{ salsa::{Database, ParallelDatabase, Snapshot}, Cancelled, CrateGraph, CrateId, SourceDatabase, SourceDatabaseExt, }, - FxHashSet, FxIndexMap, + FxIndexMap, }; +use stdx::hash::NoHashHashSet; use crate::RootDatabase; @@ -141,7 +142,7 @@ pub(crate) fn parallel_prime_caches( } } -fn compute_crates_to_prime(db: &RootDatabase, graph: &CrateGraph) -> FxHashSet<CrateId> { +fn compute_crates_to_prime(db: &RootDatabase, graph: &CrateGraph) -> NoHashHashSet<CrateId> { // We're only interested in the workspace crates and the `ImportMap`s of their direct // dependencies, though in practice the latter also compute the `DefMap`s. // We don't prime transitive dependencies because they're generally not visible in diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 1a6beec1881..99614b645e4 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -14,8 +14,9 @@ use ide_db::{ base_db::FileId, defs::{Definition, NameClass, NameRefClass}, search::{ReferenceCategory, SearchScope, UsageSearchResult}, - FxHashMap, RootDatabase, + RootDatabase, }; +use stdx::hash::NoHashHashMap; use syntax::{ algo::find_node_at_offset, ast::{self, HasName}, @@ -29,7 +30,7 @@ use crate::{FilePosition, NavigationTarget, TryToNav}; #[derive(Debug, Clone)] pub struct ReferenceSearchResult { pub declaration: Option<Declaration>, - pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceCategory>)>>, + pub references: NoHashHashMap<FileId, Vec<(TextRange, Option<ReferenceCategory>)>>, } #[derive(Debug, Clone)] diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 3fb49b45d98..50371d620eb 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -14,7 +14,7 @@ mod html; mod tests; use hir::{Name, Semantics}; -use ide_db::{FxHashMap, RootDatabase}; +use ide_db::{FxHashMap, RootDatabase, SymbolKind}; use syntax::{ ast, AstNode, AstToken, NodeOrToken, SyntaxKind::*, SyntaxNode, TextRange, WalkEvent, T, }; @@ -24,7 +24,7 @@ use crate::{ escape::highlight_escape_string, format::highlight_format_string, highlights::Highlights, macro_::MacroHighlighter, tags::Highlight, }, - FileId, HlMod, HlTag, + FileId, HlMod, HlOperator, HlPunct, HlTag, }; pub(crate) use html::highlight_as_html; @@ -36,6 +36,26 @@ pub struct HlRange { pub binding_hash: Option<u64>, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct HighlightConfig { + /// Whether to highlight strings + pub strings: bool, + /// Whether to highlight punctuation + pub punctuation: bool, + /// Whether to specialize punctuation highlights + pub specialize_punctuation: bool, + /// Whether to highlight operator + pub operator: bool, + /// Whether to specialize operator highlights + pub specialize_operator: bool, + /// Whether to inject highlights into doc comments + pub inject_doc_comment: bool, + /// Whether to highlight the macro call bang + pub macro_bang: bool, + /// Whether to highlight unresolved things be their syntax + pub syntactic_name_ref_highlighting: bool, +} + // Feature: Semantic Syntax Highlighting // // rust-analyzer highlights the code semantically. @@ -155,9 +175,9 @@ pub struct HlRange { // image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[] pub(crate) fn highlight( db: &RootDatabase, + config: HighlightConfig, file_id: FileId, range_to_highlight: Option<TextRange>, - syntactic_name_ref_highlighting: bool, ) -> Vec<HlRange> { let _p = profile::span("highlight"); let sema = Semantics::new(db); @@ -183,26 +203,18 @@ pub(crate) fn highlight( Some(it) => it.krate(), None => return hl.to_vec(), }; - traverse( - &mut hl, - &sema, - file_id, - &root, - krate, - range_to_highlight, - syntactic_name_ref_highlighting, - ); + traverse(&mut hl, &sema, config, file_id, &root, krate, range_to_highlight); hl.to_vec() } fn traverse( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, + config: HighlightConfig, file_id: FileId, root: &SyntaxNode, krate: hir::Crate, range_to_highlight: TextRange, - syntactic_name_ref_highlighting: bool, ) { let is_unlinked = sema.to_module_def(file_id).is_none(); let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); @@ -323,9 +335,11 @@ fn traverse( Enter(it) => it, Leave(NodeOrToken::Token(_)) => continue, Leave(NodeOrToken::Node(node)) => { - // Doc comment highlighting injection, we do this when leaving the node - // so that we overwrite the highlighting of the doc comment itself. - inject::doc_comment(hl, sema, file_id, &node); + if config.inject_doc_comment { + // Doc comment highlighting injection, we do this when leaving the node + // so that we overwrite the highlighting of the doc comment itself. + inject::doc_comment(hl, sema, config, file_id, &node); + } continue; } }; @@ -400,7 +414,8 @@ fn traverse( let string_to_highlight = ast::String::cast(descended_token.clone()); if let Some((string, expanded_string)) = string.zip(string_to_highlight) { if string.is_raw() { - if inject::ra_fixture(hl, sema, &string, &expanded_string).is_some() { + if inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some() + { continue; } } @@ -421,7 +436,7 @@ fn traverse( sema, krate, &mut bindings_shadow_count, - syntactic_name_ref_highlighting, + config.syntactic_name_ref_highlighting, name_like, ), NodeOrToken::Token(token) => highlight::token(sema, token).zip(Some(None)), @@ -439,6 +454,29 @@ fn traverse( // something unresolvable. FIXME: There should be a way to prevent that continue; } + + // apply config filtering + match &mut highlight.tag { + HlTag::StringLiteral if !config.strings => continue, + // If punctuation is disabled, make the macro bang part of the macro call again. + tag @ HlTag::Punctuation(HlPunct::MacroBang) => { + if !config.macro_bang { + *tag = HlTag::Symbol(SymbolKind::Macro); + } else if !config.specialize_punctuation { + *tag = HlTag::Punctuation(HlPunct::Other); + } + } + HlTag::Punctuation(_) if !config.punctuation => continue, + tag @ HlTag::Punctuation(_) if !config.specialize_punctuation => { + *tag = HlTag::Punctuation(HlPunct::Other); + } + HlTag::Operator(_) if !config.operator && highlight.mods.is_empty() => continue, + tag @ HlTag::Operator(_) if !config.specialize_operator => { + *tag = HlTag::Operator(HlOperator::Other); + } + _ => (), + } + if inside_attribute { highlight |= HlMod::Attribute } diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index 9777c014c7a..e91fd7f1257 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs @@ -5,7 +5,10 @@ use oorandom::Rand32; use stdx::format_to; use syntax::AstNode; -use crate::{syntax_highlighting::highlight, FileId, RootDatabase}; +use crate::{ + syntax_highlighting::{highlight, HighlightConfig}, + FileId, RootDatabase, +}; pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { let parse = db.parse(file_id); @@ -20,7 +23,21 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo ) } - let hl_ranges = highlight(db, file_id, None, false); + let hl_ranges = highlight( + db, + HighlightConfig { + strings: true, + punctuation: true, + specialize_punctuation: true, + specialize_operator: true, + operator: true, + inject_doc_comment: true, + macro_bang: true, + syntactic_name_ref_highlighting: false, + }, + file_id, + None, + ); let text = parse.tree().syntax().to_string(); let mut buf = String::new(); buf.push_str(STYLE); diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index f376f9fda7a..9139528c7ed 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -15,13 +15,14 @@ use syntax::{ use crate::{ doc_links::{doc_attributes, extract_definitions_from_docs, resolve_doc_path_for_def}, - syntax_highlighting::{highlights::Highlights, injector::Injector}, + syntax_highlighting::{highlights::Highlights, injector::Injector, HighlightConfig}, Analysis, HlMod, HlRange, HlTag, RootDatabase, }; pub(super) fn ra_fixture( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, + config: HighlightConfig, literal: &ast::String, expanded: &ast::String, ) -> Option<()> { @@ -63,7 +64,13 @@ pub(super) fn ra_fixture( let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text()); - for mut hl_range in analysis.highlight(tmp_file_id).unwrap() { + for mut hl_range in analysis + .highlight( + HighlightConfig { syntactic_name_ref_highlighting: false, ..config }, + tmp_file_id, + ) + .unwrap() + { for range in inj.map_range_up(hl_range.range) { if let Some(range) = literal.map_range_up(range) { hl_range.range = range; @@ -86,6 +93,7 @@ const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; pub(super) fn doc_comment( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, + config: HighlightConfig, src_file_id: FileId, node: &SyntaxNode, ) { @@ -206,7 +214,14 @@ pub(super) fn doc_comment( let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text()); - if let Ok(ranges) = analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)) { + if let Ok(ranges) = analysis.with_db(|db| { + super::highlight( + db, + HighlightConfig { syntactic_name_ref_highlighting: true, ..config }, + tmp_file_id, + None, + ) + }) { for HlRange { range, highlight, binding_hash } in ranges { for range in inj.map_range_up(range) { hl.add(HlRange { range, highlight: highlight | HlMod::Injected, binding_hash }); diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 5262770f303..3949f1189bd 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs @@ -199,7 +199,7 @@ impl fmt::Display for HlTag { } impl HlMod { - const ALL: &'static [HlMod; HlMod::Unsafe as u8 as usize + 1] = &[ + const ALL: &'static [HlMod; 19] = &[ HlMod::Associated, HlMod::Async, HlMod::Attribute, @@ -296,7 +296,7 @@ impl Highlight { Highlight { tag, mods: HlMods::default() } } pub fn is_empty(&self) -> bool { - self.tag == HlTag::None && self.mods == HlMods::default() + self.tag == HlTag::None && self.mods.is_empty() } } @@ -330,6 +330,10 @@ impl ops::BitOr<HlMod> for Highlight { } impl HlMods { + pub fn is_empty(&self) -> bool { + self.0 == 0 + } + pub fn contains(self, m: HlMod) -> bool { self.0 & m.mask() == m.mask() } 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 a747b4bc1f9..eef5baea983 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -56,7 +56,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span> <span class="brace">}</span> -<span class="comment documentation">/// This is an impl with a code block.</span> +<span class="comment documentation">/// This is an impl of </span><span class="struct documentation injected intra_doc_link">[`Foo`]</span><span class="comment documentation"> with a code block.</span> <span class="comment documentation">///</span> <span class="comment documentation">/// ```</span> <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span> diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 382735cb368..46cc667fc45 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -4,7 +4,18 @@ use expect_test::{expect_file, ExpectFile}; use ide_db::SymbolKind; use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear}; -use crate::{fixture, FileRange, HlTag, TextRange}; +use crate::{fixture, FileRange, HighlightConfig, HlTag, TextRange}; + +const HL_CONFIG: HighlightConfig = HighlightConfig { + strings: true, + punctuation: true, + specialize_punctuation: true, + specialize_operator: true, + operator: true, + inject_doc_comment: true, + macro_bang: true, + syntactic_name_ref_highlighting: false, +}; #[test] fn attributes() { @@ -613,7 +624,7 @@ struct Foo { bar: bool, } -/// This is an impl with a code block. +/// This is an impl of [`Foo`] with a code block. /// /// ``` /// fn foo() { @@ -996,7 +1007,10 @@ struct Foo { // The "x" let highlights = &analysis - .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) }) + .highlight_range( + HL_CONFIG, + FileRange { file_id, range: TextRange::at(45.into(), 1.into()) }, + ) .unwrap(); assert_eq!(&highlights[0].highlight.to_string(), "field.declaration.public"); @@ -1011,7 +1025,7 @@ macro_rules! test {} }"# .trim(), ); - let _ = analysis.highlight(file_id).unwrap(); + let _ = analysis.highlight(HL_CONFIG, file_id).unwrap(); } /// Highlights the code given by the `ra_fixture` argument, renders the @@ -1035,7 +1049,7 @@ fn benchmark_syntax_highlighting_long_struct() { let hash = { let _pt = bench("syntax highlighting long struct"); analysis - .highlight(file_id) + .highlight(HL_CONFIG, file_id) .unwrap() .iter() .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) @@ -1061,7 +1075,7 @@ fn syntax_highlighting_not_quadratic() { let time = Instant::now(); let hash = analysis - .highlight(file_id) + .highlight(HL_CONFIG, file_id) .unwrap() .iter() .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) @@ -1086,7 +1100,7 @@ fn benchmark_syntax_highlighting_parser() { let hash = { let _pt = bench("syntax highlighting parser"); analysis - .highlight(file_id) + .highlight(HL_CONFIG, file_id) .unwrap() .iter() .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function)) diff --git a/crates/ide/src/view_crate_graph.rs b/crates/ide/src/view_crate_graph.rs index bf7b7efe282..17a1e385b77 100644 --- a/crates/ide/src/view_crate_graph.rs +++ b/crates/ide/src/view_crate_graph.rs @@ -3,8 +3,9 @@ use std::sync::Arc; use dot::{Id, LabelText}; use ide_db::{ base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt}, - FxHashSet, RootDatabase, + RootDatabase, }; +use stdx::hash::NoHashHashSet; // Feature: View Crate Graph // @@ -41,7 +42,7 @@ pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> Result<String, struct DotCrateGraph { graph: Arc<CrateGraph>, - crates_to_render: FxHashSet<CrateId>, + crates_to_render: NoHashHashSet<CrateId>, } type Edge<'a> = (CrateId, &'a Dependency); diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs index 7e21a808da0..bc1224af9b2 100644 --- a/crates/parser/src/grammar/patterns.rs +++ b/crates/parser/src/grammar/patterns.rs @@ -13,6 +13,8 @@ pub(super) const PATTERN_FIRST: TokenSet = T![.], ])); +const PAT_TOP_FIRST: TokenSet = PATTERN_FIRST.union(TokenSet::new(&[T![|]])); + pub(crate) fn pattern(p: &mut Parser<'_>) { pattern_r(p, PAT_RECOVERY_SET); } @@ -228,6 +230,7 @@ fn path_or_macro_pat(p: &mut Parser<'_>) -> CompletedMarker { // let S(_) = (); // let S(_,) = (); // let S(_, .. , x) = (); +// let S(| a) = (); // } fn tuple_pat_fields(p: &mut Parser<'_>) { assert!(p.at(T!['('])); @@ -363,6 +366,7 @@ fn ref_pat(p: &mut Parser<'_>) -> CompletedMarker { // let (a,) = (); // let (..) = (); // let () = (); +// let (| a | a, | b) = ((),()); // } fn tuple_pat(p: &mut Parser<'_>) -> CompletedMarker { assert!(p.at(T!['('])); @@ -373,13 +377,13 @@ fn tuple_pat(p: &mut Parser<'_>) -> CompletedMarker { let mut has_rest = false; while !p.at(EOF) && !p.at(T![')']) { has_pat = true; - if !p.at_ts(PATTERN_FIRST) { + if !p.at_ts(PAT_TOP_FIRST) { p.error("expected a pattern"); break; } has_rest |= p.at(T![..]); - pattern(p); + pattern_top(p); if !p.at(T![')']) { has_comma = true; p.expect(T![,]); @@ -393,6 +397,7 @@ fn tuple_pat(p: &mut Parser<'_>) -> CompletedMarker { // test slice_pat // fn main() { // let [a, b, ..] = []; +// let [| a, ..] = []; // } fn slice_pat(p: &mut Parser<'_>) -> CompletedMarker { assert!(p.at(T!['['])); @@ -405,12 +410,12 @@ fn slice_pat(p: &mut Parser<'_>) -> CompletedMarker { fn pat_list(p: &mut Parser<'_>, ket: SyntaxKind) { while !p.at(EOF) && !p.at(ket) { - if !p.at_ts(PATTERN_FIRST) { + if !p.at_ts(PAT_TOP_FIRST) { p.error("expected a pattern"); break; } - pattern(p); + pattern_top(p); if !p.at(ket) { p.expect(T![,]); } diff --git a/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rast b/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rast index 235a9d7f404..dff72ba886f 100644 --- a/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rast +++ b/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rast @@ -37,6 +37,29 @@ SOURCE_FILE L_BRACK "[" R_BRACK "]" SEMICOLON ";" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + SLICE_PAT + L_BRACK "[" + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "a" + COMMA "," + WHITESPACE " " + REST_PAT + DOT2 ".." + R_BRACK "]" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ARRAY_EXPR + L_BRACK "[" + R_BRACK "]" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rs b/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rs index 7955973b952..855ba89b1e9 100644 --- a/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rs +++ b/crates/parser/test_data/parser/inline/ok/0024_slice_pat.rs @@ -1,3 +1,4 @@ fn main() { let [a, b, ..] = []; + let [| a, ..] = []; } diff --git a/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rast b/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rast index 3cdaf32b572..55baf2fdcb4 100644 --- a/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rast +++ b/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rast @@ -100,6 +100,29 @@ SOURCE_FILE L_PAREN "(" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + TUPLE_STRUCT_PAT + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + L_PAREN "(" + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "a" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + TUPLE_EXPR + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rs b/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rs index 0dfe6362967..8ec6f4ca93e 100644 --- a/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rs +++ b/crates/parser/test_data/parser/inline/ok/0026_tuple_pat_fields.rs @@ -3,4 +3,5 @@ fn foo() { let S(_) = (); let S(_,) = (); let S(_, .. , x) = (); + let S(| a) = (); } diff --git a/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rast b/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rast index cebe98c43aa..1a01e0f6938 100644 --- a/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rast +++ b/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rast @@ -85,6 +85,46 @@ SOURCE_FILE L_PAREN "(" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + TUPLE_PAT + L_PAREN "(" + PIPE "|" + WHITESPACE " " + OR_PAT + IDENT_PAT + NAME + IDENT "a" + WHITESPACE " " + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "a" + COMMA "," + WHITESPACE " " + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "b" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + TUPLE_EXPR + L_PAREN "(" + TUPLE_EXPR + L_PAREN "(" + R_PAREN ")" + COMMA "," + TUPLE_EXPR + L_PAREN "(" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rs b/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rs index ba719879d4c..fbd7f48f66b 100644 --- a/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rs +++ b/crates/parser/test_data/parser/inline/ok/0111_tuple_pat.rs @@ -3,4 +3,5 @@ fn main() { let (a,) = (); let (..) = (); let () = (); + let (| a | a, | b) = ((),()); } diff --git a/crates/proc-macro-srv/Cargo.toml b/crates/proc-macro-srv/Cargo.toml index 5746eac0b37..e39026ac70b 100644 --- a/crates/proc-macro-srv/Cargo.toml +++ b/crates/proc-macro-srv/Cargo.toml @@ -24,7 +24,6 @@ tt = { path = "../tt", version = "0.0.0" } mbe = { path = "../mbe", version = "0.0.0" } paths = { path = "../paths", version = "0.0.0" } proc-macro-api = { path = "../proc-macro-api", version = "0.0.0" } -crossbeam = "0.8.1" [dev-dependencies] expect-test = "1.4.0" diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs index 4c205b9cada..3679bfc43c9 100644 --- a/crates/proc-macro-srv/src/lib.rs +++ b/crates/proc-macro-srv/src/lib.rs @@ -26,6 +26,7 @@ use std::{ ffi::OsString, fs, path::{Path, PathBuf}, + thread, time::SystemTime, }; @@ -65,18 +66,16 @@ impl ProcMacroSrv { let macro_body = task.macro_body.to_subtree(); let attributes = task.attributes.map(|it| it.to_subtree()); - // FIXME: replace this with std's scoped threads once they stabilize - // (then remove dependency on crossbeam) - let result = crossbeam::scope(|s| { - let res = match s - .builder() + let result = thread::scope(|s| { + let thread = thread::Builder::new() .stack_size(EXPANDER_STACK_SIZE) .name(task.macro_name.clone()) - .spawn(|_| { + .spawn_scoped(s, || { expander .expand(&task.macro_name, ¯o_body, attributes.as_ref()) .map(|it| FlatTree::new(&it)) - }) { + }); + let res = match thread { Ok(handle) => handle.join(), Err(e) => std::panic::resume_unwind(Box::new(e)), }; @@ -86,10 +85,6 @@ impl ProcMacroSrv { Err(e) => std::panic::resume_unwind(e), } }); - let result = match result { - Ok(result) => result, - Err(e) => std::panic::resume_unwind(e), - }; prev_env.rollback(); diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index 8d0fa757c2e..9ccb6e9101e 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -185,10 +185,10 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { is_proc_macro: false, }, CrateId( - 2, + 1, ): CrateData { root_file_id: FileId( - 3, + 2, ), edition: Edition2018, version: Some( @@ -197,9 +197,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { display_name: Some( CrateDisplayName { crate_name: CrateName( - "an_example", + "hello_world", ), - canonical_name: "an-example", + canonical_name: "hello-world", }, ), cfg_options: CfgOptions( @@ -260,77 +260,85 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { is_proc_macro: false, }, CrateId( - 4, + 2, ): CrateData { root_file_id: FileId( - 5, + 3, ), - edition: Edition2015, + edition: Edition2018, version: Some( - "0.2.98", + "0.1.0", ), display_name: Some( CrateDisplayName { crate_name: CrateName( - "libc", + "an_example", ), - canonical_name: "libc", + canonical_name: "an-example", }, ), cfg_options: CfgOptions( [ "debug_assertions", - "feature=default", - "feature=std", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "feature=align", - "feature=const-extern-fn", - "feature=default", - "feature=extra_traits", - "feature=rustc-dep-of-std", - "feature=std", - "feature=use_std", ], ), env: Env { entries: { "CARGO_PKG_LICENSE": "", "CARGO_PKG_VERSION_MAJOR": "0", - "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98", - "CARGO_PKG_VERSION": "0.2.98", + "CARGO_MANIFEST_DIR": "$ROOT$hello-world", + "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "libc", + "CARGO_CRATE_NAME": "hello_world", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", - "CARGO_PKG_NAME": "libc", - "CARGO_PKG_VERSION_PATCH": "98", + "CARGO_PKG_NAME": "hello-world", + "CARGO_PKG_VERSION_PATCH": "0", "CARGO": "cargo", "CARGO_PKG_REPOSITORY": "", - "CARGO_PKG_VERSION_MINOR": "2", + "CARGO_PKG_VERSION_MINOR": "1", "CARGO_PKG_VERSION_PRE": "", }, }, - dependencies: [], + dependencies: [ + Dependency { + crate_id: CrateId( + 0, + ), + name: CrateName( + "hello_world", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 4, + ), + name: CrateName( + "libc", + ), + prelude: true, + }, + ], proc_macro: Err( "crate has not (yet) been built", ), origin: CratesIo { - repo: Some( - "https://github.com/rust-lang/libc", - ), + repo: None, }, is_proc_macro: false, }, CrateId( - 1, + 3, ): CrateData { root_file_id: FileId( - 2, + 4, ), edition: Edition2018, version: Some( @@ -339,9 +347,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { display_name: Some( CrateDisplayName { crate_name: CrateName( - "hello_world", + "it", ), - canonical_name: "hello-world", + canonical_name: "it", }, ), cfg_options: CfgOptions( @@ -402,77 +410,69 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { is_proc_macro: false, }, CrateId( - 3, + 4, ): CrateData { root_file_id: FileId( - 4, + 5, ), - edition: Edition2018, + edition: Edition2015, version: Some( - "0.1.0", + "0.2.98", ), display_name: Some( CrateDisplayName { crate_name: CrateName( - "it", + "libc", ), - canonical_name: "it", + canonical_name: "libc", }, ), cfg_options: CfgOptions( [ "debug_assertions", + "feature=default", + "feature=std", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", + "feature=align", + "feature=const-extern-fn", + "feature=default", + "feature=extra_traits", + "feature=rustc-dep-of-std", + "feature=std", + "feature=use_std", ], ), env: Env { entries: { "CARGO_PKG_LICENSE": "", "CARGO_PKG_VERSION_MAJOR": "0", - "CARGO_MANIFEST_DIR": "$ROOT$hello-world", - "CARGO_PKG_VERSION": "0.1.0", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98", + "CARGO_PKG_VERSION": "0.2.98", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "libc", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", - "CARGO_PKG_NAME": "hello-world", - "CARGO_PKG_VERSION_PATCH": "0", + "CARGO_PKG_NAME": "libc", + "CARGO_PKG_VERSION_PATCH": "98", "CARGO": "cargo", "CARGO_PKG_REPOSITORY": "", - "CARGO_PKG_VERSION_MINOR": "1", + "CARGO_PKG_VERSION_MINOR": "2", "CARGO_PKG_VERSION_PRE": "", }, }, - dependencies: [ - Dependency { - crate_id: CrateId( - 0, - ), - name: CrateName( - "hello_world", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 4, - ), - name: CrateName( - "libc", - ), - prelude: true, - }, - ], + dependencies: [], proc_macro: Err( "crate has not (yet) been built", ), origin: CratesIo { - repo: None, + repo: Some( + "https://github.com/rust-lang/libc", + ), }, is_proc_macro: false, }, @@ -567,10 +567,10 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 2, + 1, ): CrateData { root_file_id: FileId( - 3, + 2, ), edition: Edition2018, version: Some( @@ -579,9 +579,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { display_name: Some( CrateDisplayName { crate_name: CrateName( - "an_example", + "hello_world", ), - canonical_name: "an-example", + canonical_name: "hello-world", }, ), cfg_options: CfgOptions( @@ -644,77 +644,87 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 4, + 2, ): CrateData { root_file_id: FileId( - 5, + 3, ), - edition: Edition2015, + edition: Edition2018, version: Some( - "0.2.98", + "0.1.0", ), display_name: Some( CrateDisplayName { crate_name: CrateName( - "libc", + "an_example", ), - canonical_name: "libc", + canonical_name: "an-example", }, ), cfg_options: CfgOptions( [ "debug_assertions", - "feature=default", - "feature=std", + "test", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "feature=align", - "feature=const-extern-fn", - "feature=default", - "feature=extra_traits", - "feature=rustc-dep-of-std", - "feature=std", - "feature=use_std", + "test", ], ), env: Env { entries: { "CARGO_PKG_LICENSE": "", "CARGO_PKG_VERSION_MAJOR": "0", - "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98", - "CARGO_PKG_VERSION": "0.2.98", + "CARGO_MANIFEST_DIR": "$ROOT$hello-world", + "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "libc", + "CARGO_CRATE_NAME": "hello_world", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", - "CARGO_PKG_NAME": "libc", - "CARGO_PKG_VERSION_PATCH": "98", + "CARGO_PKG_NAME": "hello-world", + "CARGO_PKG_VERSION_PATCH": "0", "CARGO": "cargo", "CARGO_PKG_REPOSITORY": "", - "CARGO_PKG_VERSION_MINOR": "2", + "CARGO_PKG_VERSION_MINOR": "1", "CARGO_PKG_VERSION_PRE": "", }, }, - dependencies: [], + dependencies: [ + Dependency { + crate_id: CrateId( + 0, + ), + name: CrateName( + "hello_world", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 4, + ), + name: CrateName( + "libc", + ), + prelude: true, + }, + ], proc_macro: Err( "crate has not (yet) been built", ), origin: CratesIo { - repo: Some( - "https://github.com/rust-lang/libc", - ), + repo: None, }, is_proc_macro: false, }, CrateId( - 1, + 3, ): CrateData { root_file_id: FileId( - 2, + 4, ), edition: Edition2018, version: Some( @@ -723,9 +733,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { display_name: Some( CrateDisplayName { crate_name: CrateName( - "hello_world", + "it", ), - canonical_name: "hello-world", + canonical_name: "it", }, ), cfg_options: CfgOptions( @@ -788,79 +798,69 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 3, + 4, ): CrateData { root_file_id: FileId( - 4, + 5, ), - edition: Edition2018, + edition: Edition2015, version: Some( - "0.1.0", + "0.2.98", ), display_name: Some( CrateDisplayName { crate_name: CrateName( - "it", + "libc", ), - canonical_name: "it", + canonical_name: "libc", }, ), cfg_options: CfgOptions( [ "debug_assertions", - "test", + "feature=default", + "feature=std", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "test", + "feature=align", + "feature=const-extern-fn", + "feature=default", + "feature=extra_traits", + "feature=rustc-dep-of-std", + "feature=std", + "feature=use_std", ], ), env: Env { entries: { "CARGO_PKG_LICENSE": "", "CARGO_PKG_VERSION_MAJOR": "0", - "CARGO_MANIFEST_DIR": "$ROOT$hello-world", - "CARGO_PKG_VERSION": "0.1.0", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98", + "CARGO_PKG_VERSION": "0.2.98", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "libc", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", - "CARGO_PKG_NAME": "hello-world", - "CARGO_PKG_VERSION_PATCH": "0", + "CARGO_PKG_NAME": "libc", + "CARGO_PKG_VERSION_PATCH": "98", "CARGO": "cargo", "CARGO_PKG_REPOSITORY": "", - "CARGO_PKG_VERSION_MINOR": "1", + "CARGO_PKG_VERSION_MINOR": "2", "CARGO_PKG_VERSION_PRE": "", }, }, - dependencies: [ - Dependency { - crate_id: CrateId( - 0, - ), - name: CrateName( - "hello_world", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 4, - ), - name: CrateName( - "libc", - ), - prelude: true, - }, - ], + dependencies: [], proc_macro: Err( "crate has not (yet) been built", ), origin: CratesIo { - repo: None, + repo: Some( + "https://github.com/rust-lang/libc", + ), }, is_proc_macro: false, }, @@ -946,10 +946,10 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 2, + 1, ): CrateData { root_file_id: FileId( - 3, + 2, ), edition: Edition2018, version: Some( @@ -958,9 +958,9 @@ fn cargo_hello_world_project_model() { display_name: Some( CrateDisplayName { crate_name: CrateName( - "an_example", + "hello_world", ), - canonical_name: "an-example", + canonical_name: "hello-world", }, ), cfg_options: CfgOptions( @@ -1023,77 +1023,87 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 4, + 2, ): CrateData { root_file_id: FileId( - 5, + 3, ), - edition: Edition2015, + edition: Edition2018, version: Some( - "0.2.98", + "0.1.0", ), display_name: Some( CrateDisplayName { crate_name: CrateName( - "libc", + "an_example", ), - canonical_name: "libc", + canonical_name: "an-example", }, ), cfg_options: CfgOptions( [ "debug_assertions", - "feature=default", - "feature=std", + "test", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "feature=align", - "feature=const-extern-fn", - "feature=default", - "feature=extra_traits", - "feature=rustc-dep-of-std", - "feature=std", - "feature=use_std", + "test", ], ), env: Env { entries: { "CARGO_PKG_LICENSE": "", "CARGO_PKG_VERSION_MAJOR": "0", - "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98", - "CARGO_PKG_VERSION": "0.2.98", + "CARGO_MANIFEST_DIR": "$ROOT$hello-world", + "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "libc", + "CARGO_CRATE_NAME": "hello_world", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", - "CARGO_PKG_NAME": "libc", - "CARGO_PKG_VERSION_PATCH": "98", + "CARGO_PKG_NAME": "hello-world", + "CARGO_PKG_VERSION_PATCH": "0", "CARGO": "cargo", "CARGO_PKG_REPOSITORY": "", - "CARGO_PKG_VERSION_MINOR": "2", + "CARGO_PKG_VERSION_MINOR": "1", "CARGO_PKG_VERSION_PRE": "", }, }, - dependencies: [], + dependencies: [ + Dependency { + crate_id: CrateId( + 0, + ), + name: CrateName( + "hello_world", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 4, + ), + name: CrateName( + "libc", + ), + prelude: true, + }, + ], proc_macro: Err( "crate has not (yet) been built", ), origin: CratesIo { - repo: Some( - "https://github.com/rust-lang/libc", - ), + repo: None, }, is_proc_macro: false, }, CrateId( - 1, + 3, ): CrateData { root_file_id: FileId( - 2, + 4, ), edition: Edition2018, version: Some( @@ -1102,9 +1112,9 @@ fn cargo_hello_world_project_model() { display_name: Some( CrateDisplayName { crate_name: CrateName( - "hello_world", + "it", ), - canonical_name: "hello-world", + canonical_name: "it", }, ), cfg_options: CfgOptions( @@ -1167,79 +1177,69 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 3, + 4, ): CrateData { root_file_id: FileId( - 4, + 5, ), - edition: Edition2018, + edition: Edition2015, version: Some( - "0.1.0", + "0.2.98", ), display_name: Some( CrateDisplayName { crate_name: CrateName( - "it", + "libc", ), - canonical_name: "it", + canonical_name: "libc", }, ), cfg_options: CfgOptions( [ "debug_assertions", - "test", + "feature=default", + "feature=std", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "test", + "feature=align", + "feature=const-extern-fn", + "feature=default", + "feature=extra_traits", + "feature=rustc-dep-of-std", + "feature=std", + "feature=use_std", ], ), env: Env { entries: { "CARGO_PKG_LICENSE": "", "CARGO_PKG_VERSION_MAJOR": "0", - "CARGO_MANIFEST_DIR": "$ROOT$hello-world", - "CARGO_PKG_VERSION": "0.1.0", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98", + "CARGO_PKG_VERSION": "0.2.98", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "libc", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", - "CARGO_PKG_NAME": "hello-world", - "CARGO_PKG_VERSION_PATCH": "0", + "CARGO_PKG_NAME": "libc", + "CARGO_PKG_VERSION_PATCH": "98", "CARGO": "cargo", "CARGO_PKG_REPOSITORY": "", - "CARGO_PKG_VERSION_MINOR": "1", + "CARGO_PKG_VERSION_MINOR": "2", "CARGO_PKG_VERSION_PRE": "", }, }, - dependencies: [ - Dependency { - crate_id: CrateId( - 0, - ), - name: CrateName( - "hello_world", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 4, - ), - name: CrateName( - "libc", - ), - prelude: true, - }, - ], + dependencies: [], proc_macro: Err( "crate has not (yet) been built", ), origin: CratesIo { - repo: None, + repo: Some( + "https://github.com/rust-lang/libc", + ), }, is_proc_macro: false, }, @@ -1301,19 +1301,53 @@ fn rust_project_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 10, + 1, ): CrateData { root_file_id: FileId( - 11, + 2, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "unwind", + "core", ), - canonical_name: "unwind", + canonical_name: "core", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: Err( + "no proc macro loaded for sysroot crate", + ), + origin: Lang( + Core, + ), + is_proc_macro: false, + }, + CrateId( + 2, + ): CrateData { + root_file_id: FileId( + 3, + ), + edition: Edition2018, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "panic_abort", + ), + canonical_name: "panic_abort", }, ), cfg_options: CfgOptions( @@ -1335,19 +1369,19 @@ fn rust_project_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 7, + 3, ): CrateData { root_file_id: FileId( - 8, + 4, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "std_detect", + "panic_unwind", ), - canonical_name: "std_detect", + canonical_name: "panic_unwind", }, ), cfg_options: CfgOptions( @@ -1413,19 +1447,19 @@ fn rust_project_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 1, + 5, ): CrateData { root_file_id: FileId( - 2, + 6, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "core", + "profiler_builtins", ), - canonical_name: "core", + canonical_name: "profiler_builtins", }, ), cfg_options: CfgOptions( @@ -1442,24 +1476,24 @@ fn rust_project_hello_world_project_model() { "no proc macro loaded for sysroot crate", ), origin: Lang( - Core, + Other, ), is_proc_macro: false, }, CrateId( - 11, + 6, ): CrateData { root_file_id: FileId( - 12, + 7, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "hello_world", + "std", ), - canonical_name: "hello_world", + canonical_name: "std", }, ), cfg_options: CfgOptions( @@ -1474,6 +1508,15 @@ fn rust_project_hello_world_project_model() { dependencies: [ Dependency { crate_id: CrateId( + 0, + ), + name: CrateName( + "alloc", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( 1, ), name: CrateName( @@ -1483,19 +1526,46 @@ fn rust_project_hello_world_project_model() { }, Dependency { crate_id: CrateId( - 0, + 2, ), name: CrateName( - "alloc", + "panic_abort", ), prelude: true, }, Dependency { crate_id: CrateId( - 6, + 3, ), name: CrateName( - "std", + "panic_unwind", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 5, + ), + name: CrateName( + "profiler_builtins", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 7, + ), + name: CrateName( + "std_detect", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 8, + ), + name: CrateName( + "term", ), prelude: true, }, @@ -1506,31 +1576,40 @@ fn rust_project_hello_world_project_model() { name: CrateName( "test", ), - prelude: false, + prelude: true, + }, + Dependency { + crate_id: CrateId( + 10, + ), + name: CrateName( + "unwind", + ), + prelude: true, }, ], proc_macro: Err( - "no proc macro dylib present", + "no proc macro loaded for sysroot crate", + ), + origin: Lang( + Std, ), - origin: CratesIo { - repo: None, - }, is_proc_macro: false, }, CrateId( - 8, + 7, ): CrateData { root_file_id: FileId( - 9, + 8, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "term", + "std_detect", ), - canonical_name: "term", + canonical_name: "std_detect", }, ), cfg_options: CfgOptions( @@ -1552,19 +1631,19 @@ fn rust_project_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 5, + 8, ): CrateData { root_file_id: FileId( - 6, + 9, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "profiler_builtins", + "term", ), - canonical_name: "profiler_builtins", + canonical_name: "term", }, ), cfg_options: CfgOptions( @@ -1586,19 +1665,19 @@ fn rust_project_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 2, + 9, ): CrateData { root_file_id: FileId( - 3, + 10, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "panic_abort", + "test", ), - canonical_name: "panic_abort", + canonical_name: "test", }, ), cfg_options: CfgOptions( @@ -1615,24 +1694,24 @@ fn rust_project_hello_world_project_model() { "no proc macro loaded for sysroot crate", ), origin: Lang( - Other, + Test, ), is_proc_macro: false, }, CrateId( - 9, + 10, ): CrateData { root_file_id: FileId( - 10, + 11, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "test", + "unwind", ), - canonical_name: "test", + canonical_name: "unwind", }, ), cfg_options: CfgOptions( @@ -1649,24 +1728,24 @@ fn rust_project_hello_world_project_model() { "no proc macro loaded for sysroot crate", ), origin: Lang( - Test, + Other, ), is_proc_macro: false, }, CrateId( - 6, + 11, ): CrateData { root_file_id: FileId( - 7, + 12, ), edition: Edition2018, version: None, display_name: Some( CrateDisplayName { crate_name: CrateName( - "std", + "hello_world", ), - canonical_name: "std", + canonical_name: "hello_world", }, ), cfg_options: CfgOptions( @@ -1681,15 +1760,6 @@ fn rust_project_hello_world_project_model() { dependencies: [ Dependency { crate_id: CrateId( - 0, - ), - name: CrateName( - "alloc", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( 1, ), name: CrateName( @@ -1699,46 +1769,19 @@ fn rust_project_hello_world_project_model() { }, Dependency { crate_id: CrateId( - 2, - ), - name: CrateName( - "panic_abort", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 3, - ), - name: CrateName( - "panic_unwind", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 5, - ), - name: CrateName( - "profiler_builtins", - ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 7, + 0, ), name: CrateName( - "std_detect", + "alloc", ), prelude: true, }, Dependency { crate_id: CrateId( - 8, + 6, ), name: CrateName( - "term", + "std", ), prelude: true, }, @@ -1749,58 +1792,15 @@ fn rust_project_hello_world_project_model() { name: CrateName( "test", ), - prelude: true, - }, - Dependency { - crate_id: CrateId( - 10, - ), - name: CrateName( - "unwind", - ), - prelude: true, + prelude: false, }, ], proc_macro: Err( - "no proc macro loaded for sysroot crate", - ), - origin: Lang( - Std, - ), - is_proc_macro: false, - }, - CrateId( - 3, - ): CrateData { - root_file_id: FileId( - 4, - ), - edition: Edition2018, - version: None, - display_name: Some( - CrateDisplayName { - crate_name: CrateName( - "panic_unwind", - ), - canonical_name: "panic_unwind", - }, - ), - cfg_options: CfgOptions( - [], - ), - potential_cfg_options: CfgOptions( - [], + "no proc macro dylib present", ), - env: Env { - entries: {}, + origin: CratesIo { + repo: None, }, - dependencies: [], - proc_macro: Err( - "no proc macro loaded for sysroot crate", - ), - origin: Lang( - Other, - ), is_proc_macro: false, }, }, diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 8d6f50f5587..818bbed6af2 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -13,7 +13,7 @@ use cfg::{CfgDiff, CfgOptions}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; -use stdx::always; +use stdx::{always, hash::NoHashHashMap}; use crate::{ build_scripts::BuildScriptOutput, @@ -471,7 +471,7 @@ fn project_json_to_crate_graph( .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load)); let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default(); - let crates: FxHashMap<CrateId, CrateId> = project + let crates: NoHashHashMap<CrateId, CrateId> = project .crates() .filter_map(|(crate_id, krate)| { let file_path = &krate.root_module; diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 07771d1b392..5392589186d 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -22,7 +22,8 @@ anyhow = "1.0.57" crossbeam-channel = "0.5.5" dissimilar = "1.0.4" itertools = "0.10.3" -lsp-types = { version = "0.93.0", features = ["proposed"] } +scip = "0.1.1" +lsp-types = { version = "0.93.1", features = ["proposed"] } parking_lot = "0.12.1" xflags = "0.2.4" oorandom = "11.1.3" @@ -88,5 +89,5 @@ in-rust-tree = [ "proc-macro-srv/sysroot-abi", "sourcegen/in-rust-tree", "ide/in-rust-tree", - "syntax/in-rust-tree" + "syntax/in-rust-tree", ] diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e9de23cb395..f6a68029725 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -93,6 +93,7 @@ fn try_main() -> Result<()> { flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?, flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?, flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?, + flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?, } Ok(()) } diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs index 6ccdaa86dd6..60ba67e25f9 100644 --- a/crates/rust-analyzer/src/cli.rs +++ b/crates/rust-analyzer/src/cli.rs @@ -9,6 +9,7 @@ mod analysis_stats; mod diagnostics; mod ssr; mod lsif; +mod scip; mod progress_report; diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs index 080e2fb4438..aa32654fbdc 100644 --- a/crates/rust-analyzer/src/cli/flags.rs +++ b/crates/rust-analyzer/src/cli/flags.rs @@ -112,6 +112,10 @@ xflags::xflags! { cmd lsif required path: PathBuf {} + + cmd scip + required path: PathBuf + {} } } @@ -140,6 +144,7 @@ pub enum RustAnalyzerCmd { Search(Search), ProcMacro(ProcMacro), Lsif(Lsif), + Scip(Scip), } #[derive(Debug)] @@ -207,6 +212,11 @@ pub struct Lsif { pub path: PathBuf, } +#[derive(Debug)] +pub struct Scip { + pub path: PathBuf, +} + impl RustAnalyzer { pub const HELP: &'static str = Self::HELP_; diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs new file mode 100644 index 00000000000..65cc993c45e --- /dev/null +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -0,0 +1,448 @@ +//! SCIP generator + +use std::{ + collections::{HashMap, HashSet}, + time::Instant, +}; + +use crate::line_index::{LineEndings, LineIndex, OffsetEncoding}; +use hir::Name; +use ide::{ + LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange, + TokenId, +}; +use ide_db::LineIndexDatabase; +use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; +use scip::types as scip_types; +use std::env; + +use crate::cli::{ + flags, + load_cargo::{load_workspace, LoadCargoConfig}, + Result, +}; + +impl flags::Scip { + pub fn run(self) -> Result<()> { + eprintln!("Generating SCIP start..."); + let now = Instant::now(); + let cargo_config = CargoConfig::default(); + + let no_progress = &|s| (eprintln!("rust-analyzer: Loading {}", s)); + let load_cargo_config = LoadCargoConfig { + load_out_dirs_from_check: true, + with_proc_macro: true, + prefill_caches: true, + }; + let path = vfs::AbsPathBuf::assert(env::current_dir()?.join(&self.path)); + let rootpath = path.normalize(); + let manifest = ProjectManifest::discover_single(&path)?; + + let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; + + let (host, vfs, _) = load_workspace(workspace, &load_cargo_config)?; + let db = host.raw_database(); + let analysis = host.analysis(); + + let si = StaticIndex::compute(&analysis); + + let mut index = scip_types::Index { + metadata: Some(scip_types::Metadata { + version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(), + tool_info: Some(scip_types::ToolInfo { + name: "rust-analyzer".to_owned(), + version: "0.1".to_owned(), + arguments: vec![], + ..Default::default() + }) + .into(), + project_root: format!( + "file://{}", + path.normalize() + .as_os_str() + .to_str() + .ok_or(anyhow::anyhow!("Unable to normalize project_root path"))? + .to_string() + ), + text_document_encoding: scip_types::TextEncoding::UTF8.into(), + ..Default::default() + }) + .into(), + ..Default::default() + }; + + let mut symbols_emitted: HashSet<TokenId> = HashSet::default(); + let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new(); + + for file in si.files { + let mut local_count = 0; + let mut new_local_symbol = || { + let new_symbol = scip::types::Symbol::new_local(local_count); + local_count += 1; + + new_symbol + }; + + let StaticIndexedFile { file_id, tokens, .. } = file; + let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) { + Some(relative_path) => relative_path, + None => continue, + }; + + let line_index = LineIndex { + index: db.line_index(file_id), + encoding: OffsetEncoding::Utf8, + endings: LineEndings::Unix, + }; + + let mut doc = scip_types::Document { + relative_path, + language: "rust".to_string(), + ..Default::default() + }; + + tokens.into_iter().for_each(|(range, id)| { + let token = si.tokens.get(id).unwrap(); + + let mut occurrence = scip_types::Occurrence::default(); + occurrence.range = text_range_to_scip_range(&line_index, range); + occurrence.symbol = match tokens_to_symbol.get(&id) { + Some(symbol) => symbol.clone(), + None => { + let symbol = match &token.moniker { + Some(moniker) => moniker_to_symbol(&moniker), + None => new_local_symbol(), + }; + + let symbol = scip::symbol::format_symbol(symbol); + tokens_to_symbol.insert(id, symbol.clone()); + symbol + } + }; + + if let Some(def) = token.definition { + if def.range == range { + occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32; + } + + if !symbols_emitted.contains(&id) { + symbols_emitted.insert(id); + + let mut symbol_info = scip_types::SymbolInformation::default(); + symbol_info.symbol = occurrence.symbol.clone(); + if let Some(hover) = &token.hover { + if !hover.markup.as_str().is_empty() { + symbol_info.documentation = vec![hover.markup.as_str().to_string()]; + } + } + + doc.symbols.push(symbol_info) + } + } + + doc.occurrences.push(occurrence); + }); + + if doc.occurrences.is_empty() { + continue; + } + + index.documents.push(doc); + } + + scip::write_message_to_file("index.scip", index) + .map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?; + + eprintln!("Generating SCIP finished {:?}", now.elapsed()); + Ok(()) + } +} + +fn get_relative_filepath( + vfs: &vfs::Vfs, + rootpath: &vfs::AbsPathBuf, + file_id: ide::FileId, +) -> Option<String> { + Some(vfs.file_path(file_id).as_path()?.strip_prefix(&rootpath)?.as_ref().to_str()?.to_string()) +} + +// SCIP Ranges have a (very large) optimization that ranges if they are on the same line +// only encode as a vector of [start_line, start_col, end_col]. +// +// This transforms a line index into the optimized SCIP Range. +fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> { + let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start()); + let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end()); + + if start_line == end_line { + vec![start_line as i32, start_col as i32, end_col as i32] + } else { + vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32] + } +} + +fn new_descriptor_str( + name: &str, + suffix: scip_types::descriptor::Suffix, +) -> scip_types::Descriptor { + scip_types::Descriptor { + name: name.to_string(), + disambiguator: "".to_string(), + suffix: suffix.into(), + ..Default::default() + } +} + +fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_types::Descriptor { + let mut name = name.to_string(); + if name.contains("'") { + name = format!("`{}`", name); + } + + new_descriptor_str(name.as_str(), suffix) +} + +/// Loosely based on `def_to_moniker` +/// +/// Only returns a Symbol when it's a non-local symbol. +/// So if the visibility isn't outside of a document, then it will return None +fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol { + use scip_types::descriptor::Suffix::*; + + let package_name = moniker.package_information.name.clone(); + let version = moniker.package_information.version.clone(); + let descriptors = moniker + .identifier + .description + .iter() + .map(|desc| { + new_descriptor( + desc.name.clone(), + match desc.desc { + MonikerDescriptorKind::Namespace => Namespace, + MonikerDescriptorKind::Type => Type, + MonikerDescriptorKind::Term => Term, + MonikerDescriptorKind::Method => Method, + MonikerDescriptorKind::TypeParameter => TypeParameter, + MonikerDescriptorKind::Parameter => Parameter, + MonikerDescriptorKind::Macro => Macro, + MonikerDescriptorKind::Meta => Meta, + }, + ) + }) + .collect(); + + scip_types::Symbol { + scheme: "rust-analyzer".into(), + package: Some(scip_types::Package { + manager: "cargo".to_string(), + name: package_name, + version, + ..Default::default() + }) + .into(), + descriptors, + ..Default::default() + } +} + +#[cfg(test)] +mod test { + use super::*; + use hir::Semantics; + use ide::{AnalysisHost, FilePosition}; + use ide_db::defs::IdentClass; + use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token}; + use scip::symbol::format_symbol; + use syntax::SyntaxKind::*; + use syntax::{AstNode, T}; + + fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) { + let mut host = AnalysisHost::default(); + let change_fixture = ChangeFixture::parse(ra_fixture); + host.raw_database_mut().apply_change(change_fixture.change); + let (file_id, range_or_offset) = + change_fixture.file_position.expect("expected a marker ($0)"); + let offset = range_or_offset.expect_offset(); + (host, FilePosition { file_id, offset }) + } + + /// If expected == "", then assert that there are no symbols (this is basically local symbol) + #[track_caller] + fn check_symbol(ra_fixture: &str, expected: &str) { + let (host, position) = position(ra_fixture); + + let FilePosition { file_id, offset } = position; + + let db = host.raw_database(); + let sema = &Semantics::new(db); + let file = sema.parse(file_id).syntax().clone(); + let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { + IDENT + | INT_NUMBER + | LIFETIME_IDENT + | T![self] + | T![super] + | T![crate] + | T![Self] + | COMMENT => 2, + kind if kind.is_trivia() => 0, + _ => 1, + }) + .expect("OK OK"); + + let navs = sema + .descend_into_macros(original_token.clone()) + .into_iter() + .filter_map(|token| { + IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| { + it.into_iter().flat_map(|def| { + let module = def.module(db).unwrap(); + let current_crate = module.krate(); + + match MonikerResult::from_def(sema.db, def, current_crate) { + Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)), + None => None, + } + }) + }) + }) + .flatten() + .collect::<Vec<_>>(); + + if expected == "" { + assert_eq!(0, navs.len(), "must have no symbols {:?}", navs); + return; + } + + assert_eq!(1, navs.len(), "must have one symbol {:?}", navs); + + let res = navs.get(0).unwrap(); + let formatted = format_symbol(res.clone()); + assert_eq!(formatted, expected); + } + + #[test] + fn basic() { + check_symbol( + r#" +//- /lib.rs crate:main deps:foo +use foo::example_mod::func; +fn main() { + func$0(); +} +//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git +pub mod example_mod { + pub fn func() {} +} +"#, + "rust-analyzer cargo foo 0.1.0 example_mod/func().", + ); + } + + #[test] + fn symbol_for_trait() { + check_symbol( + r#" +//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git +pub mod module { + pub trait MyTrait { + pub fn func$0() {} + } +} +"#, + "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().", + ); + } + + #[test] + fn symbol_for_trait_constant() { + check_symbol( + r#" + //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git + pub mod module { + pub trait MyTrait { + const MY_CONST$0: u8; + } + } + "#, + "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.", + ); + } + + #[test] + fn symbol_for_trait_type() { + check_symbol( + r#" + //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git + pub mod module { + pub trait MyTrait { + type MyType$0; + } + } + "#, + // "foo::module::MyTrait::MyType", + "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]", + ); + } + + #[test] + fn symbol_for_trait_impl_function() { + check_symbol( + r#" + //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git + pub mod module { + pub trait MyTrait { + pub fn func() {} + } + + struct MyStruct {} + + impl MyTrait for MyStruct { + pub fn func$0() {} + } + } + "#, + // "foo::module::MyStruct::MyTrait::func", + "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().", + ); + } + + #[test] + fn symbol_for_field() { + check_symbol( + r#" + //- /lib.rs crate:main deps:foo + use foo::St; + fn main() { + let x = St { a$0: 2 }; + } + //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git + pub struct St { + pub a: i32, + } + "#, + "rust-analyzer cargo foo 0.1.0 St#a.", + ); + } + + #[test] + fn local_symbol_for_local() { + check_symbol( + r#" + //- /lib.rs crate:main deps:foo + use foo::module::func; + fn main() { + func(); + } + //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git + pub mod module { + pub fn func() { + let x$0 = 2; + } + } + "#, + "", + ); + } +} diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e63cfb163ca..54dcb42d99c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -12,8 +12,8 @@ use std::{ffi::OsString, fmt, iter, path::PathBuf}; use flycheck::FlycheckConfig; use ide::{ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, - HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig, - Snippet, SnippetScope, + HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, + JoinLinesConfig, Snippet, SnippetScope, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, @@ -45,7 +45,8 @@ mod patch_old_style; // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command // Defines the server-side configuration of the rust-analyzer. We generate -// *parts* of VS Code's `package.json` config from this. +// *parts* of VS Code's `package.json` config from this. Run `cargo test` to +// re-generate that file. // // However, editor specific config, which the server doesn't know about, should // be specified directly in `package.json`. @@ -120,6 +121,10 @@ config_data! { /// Cargo, you might also want to change /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`. /// + /// If there are multiple linked projects, this command is invoked for + /// each of them, with the working directory being the project root + /// (i.e., the folder containing the `Cargo.toml`). + /// /// An example command would be: /// /// ```bash @@ -380,6 +385,34 @@ config_data! { /// available on a nightly build. rustfmt_rangeFormatting_enable: bool = "false", + /// Inject additional highlighting into doc comments. + /// + /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra + /// doc links. + semanticHighlighting_doc_comment_inject_enable: bool = "true", + /// Use semantic tokens for operators. + /// + /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when + /// they are tagged with modifiers. + semanticHighlighting_operator_enable: bool = "true", + /// Use specialized semantic tokens for operators. + /// + /// When enabled, rust-analyzer will emit special token types for operator tokens instead + /// of the generic `operator` token type. + semanticHighlighting_operator_specialization_enable: bool = "false", + /// Use semantic tokens for punctuations. + /// + /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when + /// they are tagged with modifiers or have a special role. + semanticHighlighting_punctuation_enable: bool = "false", + /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro + /// calls. + semanticHighlighting_punctuation_separate_macro_bang: bool = "false", + /// Use specialized semantic tokens for punctuations. + /// + /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead + /// of the generic `punctuation` token type. + semanticHighlighting_punctuation_specialization_enable: bool = "false", /// Use semantic tokens for strings. /// /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars. @@ -1166,8 +1199,19 @@ impl Config { } } - pub fn highlighting_strings(&self) -> bool { - self.data.semanticHighlighting_strings_enable + pub fn highlighting_config(&self) -> HighlightConfig { + HighlightConfig { + strings: self.data.semanticHighlighting_strings_enable, + punctuation: self.data.semanticHighlighting_punctuation_enable, + specialize_punctuation: self + .data + .semanticHighlighting_punctuation_specialization_enable, + macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang, + operator: self.data.semanticHighlighting_operator_enable, + specialize_operator: self.data.semanticHighlighting_operator_specialization_enable, + inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable, + syntactic_name_ref_highlighting: false, + } } pub fn hover(&self) -> HoverConfig { diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 09150c77d7d..f516c194da4 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -4,11 +4,12 @@ pub(crate) mod to_proto; use std::{mem, sync::Arc}; use ide::FileId; -use rustc_hash::{FxHashMap, FxHashSet}; +use ide_db::FxHashMap; +use stdx::hash::{NoHashHashMap, NoHashHashSet}; use crate::lsp_ext; -pub(crate) type CheckFixes = Arc<FxHashMap<usize, FxHashMap<FileId, Vec<Fix>>>>; +pub(crate) type CheckFixes = Arc<NoHashHashMap<usize, NoHashHashMap<FileId, Vec<Fix>>>>; #[derive(Debug, Default, Clone)] pub struct DiagnosticsMapConfig { @@ -19,12 +20,12 @@ pub struct DiagnosticsMapConfig { #[derive(Debug, Default, Clone)] pub(crate) struct DiagnosticCollection { - // FIXME: should be FxHashMap<FileId, Vec<ra_id::Diagnostic>> - pub(crate) native: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>, + // FIXME: should be NoHashHashMap<FileId, Vec<ra_id::Diagnostic>> + pub(crate) native: NoHashHashMap<FileId, Vec<lsp_types::Diagnostic>>, // FIXME: should be Vec<flycheck::Diagnostic> - pub(crate) check: FxHashMap<usize, FxHashMap<FileId, Vec<lsp_types::Diagnostic>>>, + pub(crate) check: NoHashHashMap<usize, NoHashHashMap<FileId, Vec<lsp_types::Diagnostic>>>, pub(crate) check_fixes: CheckFixes, - changes: FxHashSet<FileId>, + changes: NoHashHashSet<FileId>, } #[derive(Debug, Clone)] @@ -105,7 +106,7 @@ impl DiagnosticCollection { native.chain(check) } - pub(crate) fn take_changes(&mut self) -> Option<FxHashSet<FileId>> { + pub(crate) fn take_changes(&mut self) -> Option<NoHashHashSet<FileId>> { if self.changes.is_empty() { return None; } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index c55bbbbe6ef..706e1742dff 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -14,6 +14,7 @@ use parking_lot::{Mutex, RwLock}; use proc_macro_api::ProcMacroServer; use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts}; use rustc_hash::FxHashMap; +use stdx::hash::NoHashHashMap; use vfs::AnchoredPathBuf; use crate::{ @@ -67,7 +68,7 @@ pub(crate) struct GlobalState { pub(crate) flycheck_sender: Sender<flycheck::Message>, pub(crate) flycheck_receiver: Receiver<flycheck::Message>, - pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, + pub(crate) vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>, pub(crate) vfs_config_version: u32, pub(crate) vfs_progress_config_version: u32, pub(crate) vfs_progress_n_total: usize, @@ -113,7 +114,7 @@ pub(crate) struct GlobalStateSnapshot { pub(crate) check_fixes: CheckFixes, mem_docs: MemDocs, pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, - vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, + vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>, pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, } @@ -157,7 +158,7 @@ impl GlobalState { flycheck_sender, flycheck_receiver, - vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), + vfs: Arc::new(RwLock::new((vfs::Vfs::default(), NoHashHashMap::default()))), vfs_config_version: 0, vfs_progress_config_version: 0, vfs_progress_n_total: 0, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 6337d49c240..d89f0f5a3cf 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -51,6 +51,12 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result< Ok(()) } +pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> { + let _p = profile::span("handle_stop_flycheck"); + state.flycheck.iter().for_each(|flycheck| flycheck.cancel()); + Ok(()) +} + pub(crate) fn handle_analyzer_status( snap: GlobalStateSnapshot, params: lsp_ext::AnalyzerStatusParams, @@ -1498,10 +1504,8 @@ pub(crate) fn handle_semantic_tokens_full( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let highlights = snap.analysis.highlight(file_id)?; - let highlight_strings = snap.config.highlighting_strings(); - let semantic_tokens = - to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings); + let highlights = snap.analysis.highlight(snap.config.highlighting_config(), file_id)?; + let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); // Unconditionally cache the tokens snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens.clone()); @@ -1519,10 +1523,8 @@ pub(crate) fn handle_semantic_tokens_full_delta( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let highlights = snap.analysis.highlight(file_id)?; - let highlight_strings = snap.config.highlighting_strings(); - let semantic_tokens = - to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings); + let highlights = snap.analysis.highlight(snap.config.highlighting_config(), file_id)?; + let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); let mut cache = snap.semantic_tokens_cache.lock(); let cached_tokens = cache.entry(params.text_document.uri).or_default(); @@ -1550,10 +1552,8 @@ pub(crate) fn handle_semantic_tokens_range( let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; - let highlights = snap.analysis.highlight_range(frange)?; - let highlight_strings = snap.config.highlighting_strings(); - let semantic_tokens = - to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings); + let highlights = snap.analysis.highlight_range(snap.config.highlighting_config(), frange)?; + let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } @@ -1771,7 +1771,7 @@ fn run_rustfmt( let line_index = snap.file_line_index(file_id)?; - let mut rustfmt = match snap.config.rustfmt() { + let mut command = match snap.config.rustfmt() { RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => { let mut cmd = process::Command::new(toolchain::rustfmt()); cmd.args(extra_args); @@ -1836,12 +1836,12 @@ fn run_rustfmt( } }; - let mut rustfmt = rustfmt + let mut rustfmt = command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .context(format!("Failed to spawn {:?}", rustfmt))?; + .context(format!("Failed to spawn {:?}", command))?; rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; @@ -1860,7 +1860,11 @@ fn run_rustfmt( // formatting because otherwise an error is surfaced to the user on top of the // syntax error diagnostics they're already receiving. This is especially jarring // if they have format on save enabled. - tracing::info!("rustfmt exited with status 1, assuming parse error and ignoring"); + tracing::warn!( + ?command, + %captured_stderr, + "rustfmt exited with status 1" + ); Ok(None) } _ => { diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 5f0e108624b..e61c8b643d2 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -129,6 +129,14 @@ pub struct ExpandedMacro { pub expansion: String, } +pub enum CancelFlycheck {} + +impl Request for CancelFlycheck { + type Params = (); + type Result = (); + const METHOD: &'static str = "rust-analyzer/cancelFlycheck"; +} + pub enum MatchingBrace {} impl Request for MatchingBrace { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 77419998249..3cfbc2e4e45 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -288,7 +288,7 @@ impl GlobalState { if became_quiescent { // Project has loaded properly, kick off initial flycheck - self.flycheck.iter().for_each(FlycheckHandle::update); + self.flycheck.iter().for_each(FlycheckHandle::restart); if self.config.prefill_caches() { self.prime_caches_queue.request_op("became quiescent".to_string()); } @@ -328,8 +328,33 @@ impl GlobalState { } let uri = file_id_to_url(&self.vfs.read().0, file_id); - let diagnostics = + let mut diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect::<Vec<_>>(); + + // VSCode assumes diagnostic messages to be non-empty strings, so we need to patch + // empty diagnostics. Neither the docs of VSCode nor the LSP spec say whether + // diagnostic messages are actually allowed to be empty or not and patching this + // in the VSCode client does not work as the assertion happens in the protocol + // conversion. So this hack is here to stay, and will be considered a hack + // until the LSP decides to state that empty messages are allowed. + + // See https://github.com/rust-lang/rust-analyzer/issues/11404 + // See https://github.com/rust-lang/rust-analyzer/issues/13130 + let patch_empty = |message: &mut String| { + if message.is_empty() { + *message = " ".to_string(); + } + }; + + for d in &mut diagnostics { + patch_empty(&mut d.message); + if let Some(dri) = &mut d.related_information { + for dri in dri { + patch_empty(&mut dri.message); + } + } + } + let version = from_proto::vfs_path(&uri) .map(|path| self.mem_docs.get(&path).map(|it| it.version)) .unwrap_or_default(); @@ -529,6 +554,13 @@ impl GlobalState { } flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)), flycheck::Progress::DidCancel => (Progress::End, None), + flycheck::Progress::DidFailToRestart(err) => { + self.show_and_log_error( + "cargo check failed".to_string(), + Some(err.to_string()), + ); + return; + } flycheck::Progress::DidFinish(result) => { if let Err(err) = result { self.show_and_log_error( @@ -590,6 +622,7 @@ impl GlobalState { .on_sync_mut::<lsp_ext::ReloadWorkspace>(handlers::handle_workspace_reload) .on_sync_mut::<lsp_ext::MemoryUsage>(handlers::handle_memory_usage) .on_sync_mut::<lsp_ext::ShuffleCrateGraph>(handlers::handle_shuffle_crate_graph) + .on_sync_mut::<lsp_ext::CancelFlycheck>(handlers::handle_cancel_flycheck) .on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines) .on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter) .on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range) @@ -779,7 +812,7 @@ impl GlobalState { for (id, _) in workspace_ids.clone() { if id == flycheck.id() { updated = true; - flycheck.update(); + flycheck.restart(); continue; } } @@ -798,7 +831,7 @@ impl GlobalState { // No specific flycheck was triggered, so let's trigger all of them. if !updated { for flycheck in &this.flycheck { - flycheck.update(); + flycheck.restart(); } } Ok(()) diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs index 6c78b5df1a7..c48410ed55e 100644 --- a/crates/rust-analyzer/src/semantic_tokens.rs +++ b/crates/rust-analyzer/src/semantic_tokens.rs @@ -8,107 +8,130 @@ use lsp_types::{ }; macro_rules! define_semantic_token_types { - ($(($ident:ident, $string:literal)),*$(,)?) => { - $(pub(crate) const $ident: SemanticTokenType = SemanticTokenType::new($string);)* + ( + standard { + $($standard:ident),*$(,)? + } + custom { + $(($custom:ident, $string:literal)),*$(,)? + } + + ) => { + $(pub(crate) const $standard: SemanticTokenType = SemanticTokenType::$standard;)* + $(pub(crate) const $custom: SemanticTokenType = SemanticTokenType::new($string);)* pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[ - SemanticTokenType::COMMENT, - SemanticTokenType::KEYWORD, - SemanticTokenType::STRING, - SemanticTokenType::NUMBER, - SemanticTokenType::REGEXP, - SemanticTokenType::OPERATOR, - SemanticTokenType::NAMESPACE, - SemanticTokenType::TYPE, - SemanticTokenType::STRUCT, - SemanticTokenType::CLASS, - SemanticTokenType::INTERFACE, - SemanticTokenType::ENUM, - SemanticTokenType::ENUM_MEMBER, - SemanticTokenType::TYPE_PARAMETER, - SemanticTokenType::FUNCTION, - SemanticTokenType::METHOD, - SemanticTokenType::PROPERTY, - SemanticTokenType::MACRO, - SemanticTokenType::VARIABLE, - SemanticTokenType::PARAMETER, - $($ident),* + $(SemanticTokenType::$standard,)* + $($custom),* ]; }; } define_semantic_token_types![ - (ANGLE, "angle"), - (ARITHMETIC, "arithmetic"), - (ATTRIBUTE, "attribute"), - (ATTRIBUTE_BRACKET, "attributeBracket"), - (BITWISE, "bitwise"), - (BOOLEAN, "boolean"), - (BRACE, "brace"), - (BRACKET, "bracket"), - (BUILTIN_ATTRIBUTE, "builtinAttribute"), - (BUILTIN_TYPE, "builtinType"), - (CHAR, "character"), - (COLON, "colon"), - (COMMA, "comma"), - (COMPARISON, "comparison"), - (CONST_PARAMETER, "constParameter"), - (DERIVE, "derive"), - (DERIVE_HELPER, "deriveHelper"), - (DOT, "dot"), - (ESCAPE_SEQUENCE, "escapeSequence"), - (FORMAT_SPECIFIER, "formatSpecifier"), - (GENERIC, "generic"), - (LABEL, "label"), - (LIFETIME, "lifetime"), - (LOGICAL, "logical"), - (MACRO_BANG, "macroBang"), - (OPERATOR, "operator"), - (PARENTHESIS, "parenthesis"), - (PUNCTUATION, "punctuation"), - (SELF_KEYWORD, "selfKeyword"), - (SELF_TYPE_KEYWORD, "selfTypeKeyword"), - (SEMICOLON, "semicolon"), - (TYPE_ALIAS, "typeAlias"), - (TOOL_MODULE, "toolModule"), - (UNION, "union"), - (UNRESOLVED_REFERENCE, "unresolvedReference"), + standard { + COMMENT, + DECORATOR, + ENUM_MEMBER, + ENUM, + FUNCTION, + INTERFACE, + KEYWORD, + MACRO, + METHOD, + NAMESPACE, + NUMBER, + OPERATOR, + PARAMETER, + PROPERTY, + STRING, + STRUCT, + TYPE_PARAMETER, + VARIABLE, + } + + custom { + (ANGLE, "angle"), + (ARITHMETIC, "arithmetic"), + (ATTRIBUTE, "attribute"), + (ATTRIBUTE_BRACKET, "attributeBracket"), + (BITWISE, "bitwise"), + (BOOLEAN, "boolean"), + (BRACE, "brace"), + (BRACKET, "bracket"), + (BUILTIN_ATTRIBUTE, "builtinAttribute"), + (BUILTIN_TYPE, "builtinType"), + (CHAR, "character"), + (COLON, "colon"), + (COMMA, "comma"), + (COMPARISON, "comparison"), + (CONST_PARAMETER, "constParameter"), + (DERIVE, "derive"), + (DERIVE_HELPER, "deriveHelper"), + (DOT, "dot"), + (ESCAPE_SEQUENCE, "escapeSequence"), + (FORMAT_SPECIFIER, "formatSpecifier"), + (GENERIC, "generic"), + (LABEL, "label"), + (LIFETIME, "lifetime"), + (LOGICAL, "logical"), + (MACRO_BANG, "macroBang"), + (PARENTHESIS, "parenthesis"), + (PUNCTUATION, "punctuation"), + (SELF_KEYWORD, "selfKeyword"), + (SELF_TYPE_KEYWORD, "selfTypeKeyword"), + (SEMICOLON, "semicolon"), + (TYPE_ALIAS, "typeAlias"), + (TOOL_MODULE, "toolModule"), + (UNION, "union"), + (UNRESOLVED_REFERENCE, "unresolvedReference"), + } ]; macro_rules! define_semantic_token_modifiers { - ($(($ident:ident, $string:literal)),*$(,)?) => { - $(pub(crate) const $ident: SemanticTokenModifier = SemanticTokenModifier::new($string);)* + ( + standard { + $($standard:ident),*$(,)? + } + custom { + $(($custom:ident, $string:literal)),*$(,)? + } + + ) => { + + $(pub(crate) const $standard: SemanticTokenModifier = SemanticTokenModifier::$standard;)* + $(pub(crate) const $custom: SemanticTokenModifier = SemanticTokenModifier::new($string);)* pub(crate) const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[ - SemanticTokenModifier::DOCUMENTATION, - SemanticTokenModifier::DECLARATION, - SemanticTokenModifier::DEFINITION, - SemanticTokenModifier::STATIC, - SemanticTokenModifier::ABSTRACT, - SemanticTokenModifier::DEPRECATED, - SemanticTokenModifier::READONLY, - SemanticTokenModifier::DEFAULT_LIBRARY, - $($ident),* + $(SemanticTokenModifier::$standard,)* + $($custom),* ]; }; } define_semantic_token_modifiers![ - (ASYNC, "async"), - (ATTRIBUTE_MODIFIER, "attribute"), - (CALLABLE, "callable"), - (CONSTANT, "constant"), - (CONSUMING, "consuming"), - (CONTROL_FLOW, "controlFlow"), - (CRATE_ROOT, "crateRoot"), - (INJECTED, "injected"), - (INTRA_DOC_LINK, "intraDocLink"), - (LIBRARY, "library"), - (MUTABLE, "mutable"), - (PUBLIC, "public"), - (REFERENCE, "reference"), - (TRAIT_MODIFIER, "trait"), - (UNSAFE, "unsafe"), + standard { + DOCUMENTATION, + DECLARATION, + STATIC, + DEFAULT_LIBRARY, + } + custom { + (ASYNC, "async"), + (ATTRIBUTE_MODIFIER, "attribute"), + (CALLABLE, "callable"), + (CONSTANT, "constant"), + (CONSUMING, "consuming"), + (CONTROL_FLOW, "controlFlow"), + (CRATE_ROOT, "crateRoot"), + (INJECTED, "injected"), + (INTRA_DOC_LINK, "intraDocLink"), + (LIBRARY, "library"), + (MUTABLE, "mutable"), + (PUBLIC, "public"), + (REFERENCE, "reference"), + (TRAIT_MODIFIER, "trait"), + (UNSAFE, "unsafe"), + } ]; #[derive(Default)] diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index e7115b0732e..102cd602950 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -517,7 +517,6 @@ pub(crate) fn semantic_tokens( text: &str, line_index: &LineIndex, highlights: Vec<HlRange>, - highlight_strings: bool, ) -> lsp_types::SemanticTokens { let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string(); let mut builder = semantic_tokens::SemanticTokensBuilder::new(id); @@ -526,10 +525,8 @@ pub(crate) fn semantic_tokens( if highlight_range.highlight.is_empty() { continue; } + let (ty, mods) = semantic_token_type_and_modifiers(highlight_range.highlight); - if !highlight_strings && ty == lsp_types::SemanticTokenType::STRING { - continue; - } let token_index = semantic_tokens::type_index(ty); let modifier_bitset = mods.0; @@ -561,55 +558,55 @@ fn semantic_token_type_and_modifiers( let mut mods = semantic_tokens::ModifierSet::default(); let type_ = match highlight.tag { HlTag::Symbol(symbol) => match symbol { - SymbolKind::Attribute => semantic_tokens::ATTRIBUTE, + SymbolKind::Attribute => semantic_tokens::DECORATOR, SymbolKind::Derive => semantic_tokens::DERIVE, SymbolKind::DeriveHelper => semantic_tokens::DERIVE_HELPER, - SymbolKind::Module => lsp_types::SemanticTokenType::NAMESPACE, + SymbolKind::Module => semantic_tokens::NAMESPACE, SymbolKind::Impl => semantic_tokens::TYPE_ALIAS, - SymbolKind::Field => lsp_types::SemanticTokenType::PROPERTY, - SymbolKind::TypeParam => lsp_types::SemanticTokenType::TYPE_PARAMETER, + SymbolKind::Field => semantic_tokens::PROPERTY, + SymbolKind::TypeParam => semantic_tokens::TYPE_PARAMETER, SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER, SymbolKind::LifetimeParam => semantic_tokens::LIFETIME, SymbolKind::Label => semantic_tokens::LABEL, - SymbolKind::ValueParam => lsp_types::SemanticTokenType::PARAMETER, + SymbolKind::ValueParam => semantic_tokens::PARAMETER, SymbolKind::SelfParam => semantic_tokens::SELF_KEYWORD, SymbolKind::SelfType => semantic_tokens::SELF_TYPE_KEYWORD, - SymbolKind::Local => lsp_types::SemanticTokenType::VARIABLE, + SymbolKind::Local => semantic_tokens::VARIABLE, SymbolKind::Function => { if highlight.mods.contains(HlMod::Associated) { - lsp_types::SemanticTokenType::METHOD + semantic_tokens::METHOD } else { - lsp_types::SemanticTokenType::FUNCTION + semantic_tokens::FUNCTION } } SymbolKind::Const => { mods |= semantic_tokens::CONSTANT; - mods |= lsp_types::SemanticTokenModifier::STATIC; - lsp_types::SemanticTokenType::VARIABLE + mods |= semantic_tokens::STATIC; + semantic_tokens::VARIABLE } SymbolKind::Static => { - mods |= lsp_types::SemanticTokenModifier::STATIC; - lsp_types::SemanticTokenType::VARIABLE + mods |= semantic_tokens::STATIC; + semantic_tokens::VARIABLE } - SymbolKind::Struct => lsp_types::SemanticTokenType::STRUCT, - SymbolKind::Enum => lsp_types::SemanticTokenType::ENUM, - SymbolKind::Variant => lsp_types::SemanticTokenType::ENUM_MEMBER, + SymbolKind::Struct => semantic_tokens::STRUCT, + SymbolKind::Enum => semantic_tokens::ENUM, + SymbolKind::Variant => semantic_tokens::ENUM_MEMBER, SymbolKind::Union => semantic_tokens::UNION, SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS, - SymbolKind::Trait => lsp_types::SemanticTokenType::INTERFACE, - SymbolKind::Macro => lsp_types::SemanticTokenType::MACRO, + SymbolKind::Trait => semantic_tokens::INTERFACE, + SymbolKind::Macro => semantic_tokens::MACRO, SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE, SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE, }, HlTag::AttributeBracket => semantic_tokens::ATTRIBUTE_BRACKET, HlTag::BoolLiteral => semantic_tokens::BOOLEAN, HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE, - HlTag::ByteLiteral | HlTag::NumericLiteral => lsp_types::SemanticTokenType::NUMBER, + HlTag::ByteLiteral | HlTag::NumericLiteral => semantic_tokens::NUMBER, HlTag::CharLiteral => semantic_tokens::CHAR, - HlTag::Comment => lsp_types::SemanticTokenType::COMMENT, + HlTag::Comment => semantic_tokens::COMMENT, HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE, HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER, - HlTag::Keyword => lsp_types::SemanticTokenType::KEYWORD, + HlTag::Keyword => semantic_tokens::KEYWORD, HlTag::None => semantic_tokens::GENERIC, HlTag::Operator(op) => match op { HlOperator::Bitwise => semantic_tokens::BITWISE, @@ -618,7 +615,7 @@ fn semantic_token_type_and_modifiers( HlOperator::Comparison => semantic_tokens::COMPARISON, HlOperator::Other => semantic_tokens::OPERATOR, }, - HlTag::StringLiteral => lsp_types::SemanticTokenType::STRING, + HlTag::StringLiteral => semantic_tokens::STRING, HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE, HlTag::Punctuation(punct) => match punct { HlPunct::Bracket => semantic_tokens::BRACKET, @@ -643,16 +640,16 @@ fn semantic_token_type_and_modifiers( HlMod::Consuming => semantic_tokens::CONSUMING, HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW, HlMod::CrateRoot => semantic_tokens::CRATE_ROOT, - HlMod::DefaultLibrary => lsp_types::SemanticTokenModifier::DEFAULT_LIBRARY, - HlMod::Definition => lsp_types::SemanticTokenModifier::DECLARATION, - HlMod::Documentation => lsp_types::SemanticTokenModifier::DOCUMENTATION, + HlMod::DefaultLibrary => semantic_tokens::DEFAULT_LIBRARY, + HlMod::Definition => semantic_tokens::DECLARATION, + HlMod::Documentation => semantic_tokens::DOCUMENTATION, HlMod::Injected => semantic_tokens::INJECTED, HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK, HlMod::Library => semantic_tokens::LIBRARY, HlMod::Mutable => semantic_tokens::MUTABLE, HlMod::Public => semantic_tokens::PUBLIC, HlMod::Reference => semantic_tokens::REFERENCE, - HlMod::Static => lsp_types::SemanticTokenModifier::STATIC, + HlMod::Static => semantic_tokens::STATIC, HlMod::Trait => semantic_tokens::TRAIT_MODIFIER, HlMod::Unsafe => semantic_tokens::UNSAFE, }; diff --git a/crates/stdx/src/hash.rs b/crates/stdx/src/hash.rs new file mode 100644 index 00000000000..9909d71bdf0 --- /dev/null +++ b/crates/stdx/src/hash.rs @@ -0,0 +1,80 @@ +//! A none hashing [`Hasher`] implementation. +use std::{ + hash::{BuildHasher, Hasher}, + marker::PhantomData, +}; + +pub type NoHashHashMap<K, V> = std::collections::HashMap<K, V, NoHashHasherBuilder<K>>; +pub type NoHashHashSet<K> = std::collections::HashSet<K, NoHashHasherBuilder<K>>; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct NoHashHasherBuilder<T>(PhantomData<T>); + +impl<T> Default for NoHashHasherBuilder<T> { + fn default() -> Self { + Self(Default::default()) + } +} + +pub trait NoHashHashable {} +impl NoHashHashable for usize {} +impl NoHashHashable for u32 {} + +pub struct NoHashHasher(u64); + +impl<T: NoHashHashable> BuildHasher for NoHashHasherBuilder<T> { + type Hasher = NoHashHasher; + fn build_hasher(&self) -> Self::Hasher { + NoHashHasher(0) + } +} + +impl Hasher for NoHashHasher { + fn finish(&self) -> u64 { + self.0 + } + + fn write(&mut self, _: &[u8]) { + unimplemented!("NoHashHasher should only be used for hashing primitive integers") + } + + fn write_u8(&mut self, i: u8) { + self.0 = i as u64; + } + + fn write_u16(&mut self, i: u16) { + self.0 = i as u64; + } + + fn write_u32(&mut self, i: u32) { + self.0 = i as u64; + } + + fn write_u64(&mut self, i: u64) { + self.0 = i as u64; + } + + fn write_usize(&mut self, i: usize) { + self.0 = i as u64; + } + + fn write_i8(&mut self, i: i8) { + self.0 = i as u64; + } + + fn write_i16(&mut self, i: i16) { + self.0 = i as u64; + } + + fn write_i32(&mut self, i: i32) { + self.0 = i as u64; + } + + fn write_i64(&mut self, i: i64) { + self.0 = i as u64; + } + + fn write_isize(&mut self, i: isize) { + self.0 = i as u64; + } +} diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index b4d45206c44..51e109798d1 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -7,6 +7,7 @@ use std::{cmp::Ordering, ops, time::Instant}; use std::{io as sio, iter}; mod macros; +pub mod hash; pub mod process; pub mod panic_context; pub mod non_empty_vec; diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 037de876d45..83f8bbac588 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -25,7 +25,7 @@ pub mod ext { return from_text(&name.text()); fn from_text(text: &str) -> ast::IdentPat { - ast_from_text(&format!("fn f({}: ())", text)) + ast_from_text(&format!("fn f({text}: ())")) } } pub fn ident_path(ident: &str) -> ast::Path { @@ -60,10 +60,10 @@ pub mod ext { expr_from_text("todo!()") } pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr { - expr_from_text(&format!("{}::default()", ty)) + expr_from_text(&format!("{ty}::default()")) } pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr { - expr_from_text(&format!("{}::new()", ty)) + expr_from_text(&format!("{ty}::new()")) } pub fn zero_number() -> ast::Expr { @@ -92,18 +92,20 @@ pub mod ext { ty_path(ident_path("bool")) } pub fn ty_option(t: ast::Type) -> ast::Type { - ty_from_text(&format!("Option<{}>", t)) + ty_from_text(&format!("Option<{t}>")) } pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type { - ty_from_text(&format!("Result<{}, {}>", t, e)) + ty_from_text(&format!("Result<{t}, {e}>")) } } -pub fn name(text: &str) -> ast::Name { - ast_from_text(&format!("mod {}{};", raw_ident_esc(text), text)) +pub fn name(name: &str) -> ast::Name { + let raw_escape = raw_ident_esc(name); + ast_from_text(&format!("mod {raw_escape}{name};")) } -pub fn name_ref(text: &str) -> ast::NameRef { - ast_from_text(&format!("fn f() {{ {}{}; }}", raw_ident_esc(text), text)) +pub fn name_ref(name_ref: &str) -> ast::NameRef { + let raw_escape = raw_ident_esc(name_ref); + ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}")) } fn raw_ident_esc(ident: &str) -> &'static str { let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some(); @@ -118,10 +120,10 @@ pub fn lifetime(text: &str) -> ast::Lifetime { let mut text = text; let tmp; if never!(!text.starts_with('\'')) { - tmp = format!("'{}", text); + tmp = format!("'{text}"); text = &tmp; } - ast_from_text(&format!("fn f<{}>() {{ }}", text)) + ast_from_text(&format!("fn f<{text}>() {{ }}")) } // FIXME: replace stringly-typed constructor with a family of typed ctors, a-la @@ -142,16 +144,16 @@ pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type { contents.push(','); } - ty_from_text(&format!("({})", contents)) + ty_from_text(&format!("({contents})")) } pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type { - ty_from_text(&if exclusive { format!("&mut {}", target) } else { format!("&{}", target) }) + ty_from_text(&if exclusive { format!("&mut {target}") } else { format!("&{target}") }) } pub fn ty_path(path: ast::Path) -> ast::Type { ty_from_text(&path.to_string()) } fn ty_from_text(text: &str) -> ast::Type { - ast_from_text(&format!("type _T = {};", text)) + ast_from_text(&format!("type _T = {text};")) } pub fn assoc_item_list() -> ast::AssocItemList { @@ -171,7 +173,7 @@ pub fn impl_( Some(params) => params.to_string(), None => String::new(), }; - ast_from_text(&format!("impl{} {}{} {{}}", params, ty, ty_params)) + ast_from_text(&format!("impl{params} {ty}{ty_params} {{}}")) } pub fn impl_trait( @@ -180,7 +182,7 @@ pub fn impl_trait( ty_params: Option<ast::GenericParamList>, ) -> ast::Impl { let ty_params = ty_params.map_or_else(String::new, |params| params.to_string()); - ast_from_text(&format!("impl{2} {} for {}{2} {{}}", trait_, ty, ty_params)) + ast_from_text(&format!("impl{ty_params} {trait_} for {ty}{ty_params} {{}}")) } pub(crate) fn generic_arg_list() -> ast::GenericArgList { @@ -188,13 +190,13 @@ pub(crate) fn generic_arg_list() -> ast::GenericArgList { } pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { - ast_from_text(&format!("type __ = {};", name_ref)) + ast_from_text(&format!("type __ = {name_ref};")) } pub fn path_segment_ty(type_ref: ast::Type, trait_ref: Option<ast::PathType>) -> ast::PathSegment { let text = match trait_ref { - Some(trait_ref) => format!("fn f(x: <{} as {}>) {{}}", type_ref, trait_ref), - None => format!("fn f(x: <{}>) {{}}", type_ref), + Some(trait_ref) => format!("fn f(x: <{type_ref} as {trait_ref}>) {{}}"), + None => format!("fn f(x: <{type_ref}>) {{}}"), }; ast_from_text(&text) } @@ -212,15 +214,15 @@ pub fn path_segment_crate() -> ast::PathSegment { } pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { - ast_from_text(&format!("type __ = {};", segment)) + ast_from_text(&format!("type __ = {segment};")) } pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { - ast_from_text(&format!("{}::{}", qual, segment)) + ast_from_text(&format!("{qual}::{segment}")) } // FIXME: path concatenation operation doesn't make sense as AST op. pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path { - ast_from_text(&format!("type __ = {}::{};", first, second)) + ast_from_text(&format!("type __ = {first}::{second};")) } pub fn path_from_segments( @@ -229,20 +231,20 @@ pub fn path_from_segments( ) -> ast::Path { let segments = segments.into_iter().map(|it| it.syntax().clone()).join("::"); ast_from_text(&if is_abs { - format!("fn f(x: ::{}) {{}}", segments) + format!("fn f(x: ::{segments}) {{}}") } else { - format!("fn f(x: {}) {{}}", segments) + format!("fn f(x: {segments}) {{}}") }) } pub fn join_paths(paths: impl IntoIterator<Item = ast::Path>) -> ast::Path { let paths = paths.into_iter().map(|it| it.syntax().clone()).join("::"); - ast_from_text(&format!("type __ = {};", paths)) + ast_from_text(&format!("type __ = {paths};")) } // FIXME: should not be pub pub fn path_from_text(text: &str) -> ast::Path { - ast_from_text(&format!("fn main() {{ let test = {}; }}", text)) + ast_from_text(&format!("fn main() {{ let test = {text}; }}")) } pub fn use_tree_glob() -> ast::UseTree { @@ -257,50 +259,50 @@ pub fn use_tree( let mut buf = "use ".to_string(); buf += &path.syntax().to_string(); if let Some(use_tree_list) = use_tree_list { - format_to!(buf, "::{}", use_tree_list); + format_to!(buf, "::{use_tree_list}"); } if add_star { buf += "::*"; } if let Some(alias) = alias { - format_to!(buf, " {}", alias); + format_to!(buf, " {alias}"); } ast_from_text(&buf) } pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList { let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", "); - ast_from_text(&format!("use {{{}}};", use_trees)) + ast_from_text(&format!("use {{{use_trees}}};")) } pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast::Use { let visibility = match visibility { None => String::new(), - Some(it) => format!("{} ", it), + Some(it) => format!("{it} "), }; - ast_from_text(&format!("{}use {};", visibility, use_tree)) + ast_from_text(&format!("{visibility}use {use_tree};")) } pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr { - ast_from_text(&format!("fn f() {{ {} {} }}", path, fields)) + ast_from_text(&format!("fn f() {{ {path} {fields} }}")) } pub fn record_expr_field_list( fields: impl IntoIterator<Item = ast::RecordExprField>, ) -> ast::RecordExprFieldList { let fields = fields.into_iter().join(", "); - ast_from_text(&format!("fn f() {{ S {{ {} }} }}", fields)) + ast_from_text(&format!("fn f() {{ S {{ {fields} }} }}")) } pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { return match expr { - Some(expr) => from_text(&format!("{}: {}", name, expr)), + Some(expr) => from_text(&format!("{name}: {expr}")), None => from_text(&name.to_string()), }; fn from_text(text: &str) -> ast::RecordExprField { - ast_from_text(&format!("fn f() {{ S {{ {}, }} }}", text)) + ast_from_text(&format!("fn f() {{ S {{ {text}, }} }}")) } } @@ -311,9 +313,9 @@ pub fn record_field( ) -> ast::RecordField { let visibility = match visibility { None => String::new(), - Some(it) => format!("{} ", it), + Some(it) => format!("{it} "), }; - ast_from_text(&format!("struct S {{ {}{}: {}, }}", visibility, name, ty)) + ast_from_text(&format!("struct S {{ {visibility}{name}: {ty}, }}")) } // TODO @@ -323,13 +325,13 @@ pub fn block_expr( ) -> ast::BlockExpr { let mut buf = "{\n".to_string(); for stmt in stmts.into_iter() { - format_to!(buf, " {}\n", stmt); + format_to!(buf, " {stmt}\n"); } if let Some(tail_expr) = tail_expr { - format_to!(buf, " {}\n", tail_expr); + format_to!(buf, " {tail_expr}\n"); } buf += "}"; - ast_from_text(&format!("fn f() {}", buf)) + ast_from_text(&format!("fn f() {buf}")) } /// Ideally this function wouldn't exist since it involves manual indenting. @@ -343,18 +345,18 @@ pub fn hacky_block_expr_with_comments( let mut buf = "{\n".to_string(); for node_or_token in elements.into_iter() { match node_or_token { - rowan::NodeOrToken::Node(n) => format_to!(buf, " {}\n", n), + rowan::NodeOrToken::Node(n) => format_to!(buf, " {n}\n"), rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::COMMENT => { - format_to!(buf, " {}\n", t) + format_to!(buf, " {t}\n") } _ => (), } } if let Some(tail_expr) = tail_expr { - format_to!(buf, " {}\n", tail_expr); + format_to!(buf, " {tail_expr}\n"); } buf += "}"; - ast_from_text(&format!("fn f() {}", buf)) + ast_from_text(&format!("fn f() {buf}")) } pub fn expr_unit() -> ast::Expr { @@ -362,7 +364,7 @@ pub fn expr_unit() -> ast::Expr { } pub fn expr_literal(text: &str) -> ast::Literal { assert_eq!(text.trim(), text); - ast_from_text(&format!("fn f() {{ let _ = {}; }}", text)) + ast_from_text(&format!("fn f() {{ let _ = {text}; }}")) } pub fn expr_empty_block() -> ast::Expr { @@ -373,41 +375,41 @@ pub fn expr_path(path: ast::Path) -> ast::Expr { } pub fn expr_continue(label: Option<ast::Lifetime>) -> ast::Expr { match label { - Some(label) => expr_from_text(&format!("continue {}", label)), + Some(label) => expr_from_text(&format!("continue {label}")), None => expr_from_text("continue"), } } // Consider `op: SyntaxKind` instead for nicer syntax at the call-site? pub fn expr_bin_op(lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { - expr_from_text(&format!("{} {} {}", lhs, op, rhs)) + expr_from_text(&format!("{lhs} {op} {rhs}")) } pub fn expr_break(label: Option<ast::Lifetime>, expr: Option<ast::Expr>) -> ast::Expr { let mut s = String::from("break"); if let Some(label) = label { - format_to!(s, " {}", label); + format_to!(s, " {label}"); } if let Some(expr) = expr { - format_to!(s, " {}", expr); + format_to!(s, " {expr}"); } expr_from_text(&s) } pub fn expr_return(expr: Option<ast::Expr>) -> ast::Expr { match expr { - Some(expr) => expr_from_text(&format!("return {}", expr)), + Some(expr) => expr_from_text(&format!("return {expr}")), None => expr_from_text("return"), } } pub fn expr_try(expr: ast::Expr) -> ast::Expr { - expr_from_text(&format!("{}?", expr)) + expr_from_text(&format!("{expr}?")) } pub fn expr_await(expr: ast::Expr) -> ast::Expr { - expr_from_text(&format!("{}.await", expr)) + expr_from_text(&format!("{expr}.await")) } pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { - expr_from_text(&format!("match {} {}", expr, match_arm_list)) + expr_from_text(&format!("match {expr} {match_arm_list}")) } pub fn expr_if( condition: ast::Expr, @@ -415,66 +417,67 @@ pub fn expr_if( else_branch: Option<ast::ElseBranch>, ) -> ast::Expr { let else_branch = match else_branch { - Some(ast::ElseBranch::Block(block)) => format!("else {}", block), - Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr), + Some(ast::ElseBranch::Block(block)) => format!("else {block}"), + Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {if_expr}"), None => String::new(), }; - expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch)) + expr_from_text(&format!("if {condition} {then_branch} {else_branch}")) } pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr { - expr_from_text(&format!("for {} in {} {}", pat, expr, block)) + expr_from_text(&format!("for {pat} in {expr} {block}")) } pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr { - expr_from_text(&format!("loop {}", block)) + expr_from_text(&format!("loop {block}")) } pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { let token = token(op); - expr_from_text(&format!("{}{}", token, expr)) + expr_from_text(&format!("{token}{expr}")) } pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { - expr_from_text(&format!("{}{}", f, arg_list)) + expr_from_text(&format!("{f}{arg_list}")) } pub fn expr_method_call( receiver: ast::Expr, method: ast::NameRef, arg_list: ast::ArgList, ) -> ast::Expr { - expr_from_text(&format!("{}.{}{}", receiver, method, arg_list)) + expr_from_text(&format!("{receiver}.{method}{arg_list}")) } pub fn expr_macro_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { - expr_from_text(&format!("{}!{}", f, arg_list)) + expr_from_text(&format!("{f}!{arg_list}")) } pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { - expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) }) + expr_from_text(&if exclusive { format!("&mut {expr}") } else { format!("&{expr}") }) } pub fn expr_closure(pats: impl IntoIterator<Item = ast::Param>, expr: ast::Expr) -> ast::Expr { let params = pats.into_iter().join(", "); - expr_from_text(&format!("|{}| {}", params, expr)) + expr_from_text(&format!("|{params}| {expr}")) } pub fn expr_field(receiver: ast::Expr, field: &str) -> ast::Expr { - expr_from_text(&format!("{}.{}", receiver, field)) + expr_from_text(&format!("{receiver}.{field}")) } pub fn expr_paren(expr: ast::Expr) -> ast::Expr { - expr_from_text(&format!("({})", expr)) + expr_from_text(&format!("({expr})")) } pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr { let expr = elements.into_iter().format(", "); - expr_from_text(&format!("({})", expr)) + expr_from_text(&format!("({expr})")) } pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { - expr_from_text(&format!("{} = {}", lhs, rhs)) + expr_from_text(&format!("{lhs} = {rhs}")) } fn expr_from_text(text: &str) -> ast::Expr { - ast_from_text(&format!("const C: () = {};", text)) + ast_from_text(&format!("const C: () = {text};")) } pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { - ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr)) + ast_from_text(&format!("const _: () = while let {pattern} = {expr} {{}};")) } pub fn arg_list(args: impl IntoIterator<Item = ast::Expr>) -> ast::ArgList { - ast_from_text(&format!("fn main() {{ ()({}) }}", args.into_iter().format(", "))) + let args = args.into_iter().format(", "); + ast_from_text(&format!("fn main() {{ ()({args}) }}")) } pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { @@ -485,7 +488,7 @@ pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { if mut_ { s.push_str("mut "); } - format_to!(s, "{}", name); + format_to!(s, "{name}"); s.push_str(": ())"); ast_from_text(&s) } @@ -494,7 +497,7 @@ pub fn wildcard_pat() -> ast::WildcardPat { return from_text("_"); fn from_text(text: &str) -> ast::WildcardPat { - ast_from_text(&format!("fn f({}: ())", text)) + ast_from_text(&format!("fn f({text}: ())")) } } @@ -502,7 +505,7 @@ pub fn literal_pat(lit: &str) -> ast::LiteralPat { return from_text(lit); fn from_text(text: &str) -> ast::LiteralPat { - ast_from_text(&format!("fn f() {{ match x {{ {} => {{}} }} }}", text)) + ast_from_text(&format!("fn f() {{ match x {{ {text} => {{}} }} }}")) } } @@ -515,10 +518,10 @@ pub fn tuple_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::TuplePat { if count == 1 { pats_str.push(','); } - return from_text(&format!("({})", pats_str)); + return from_text(&format!("({pats_str})")); fn from_text(text: &str) -> ast::TuplePat { - ast_from_text(&format!("fn f({}: ())", text)) + ast_from_text(&format!("fn f({text}: ())")) } } @@ -527,46 +530,46 @@ pub fn tuple_struct_pat( pats: impl IntoIterator<Item = ast::Pat>, ) -> ast::TupleStructPat { let pats_str = pats.into_iter().join(", "); - return from_text(&format!("{}({})", path, pats_str)); + return from_text(&format!("{path}({pats_str})")); fn from_text(text: &str) -> ast::TupleStructPat { - ast_from_text(&format!("fn f({}: ())", text)) + ast_from_text(&format!("fn f({text}: ())")) } } pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> ast::RecordPat { let pats_str = pats.into_iter().join(", "); - return from_text(&format!("{} {{ {} }}", path, pats_str)); + return from_text(&format!("{path} {{ {pats_str} }}")); fn from_text(text: &str) -> ast::RecordPat { - ast_from_text(&format!("fn f({}: ())", text)) + ast_from_text(&format!("fn f({text}: ())")) } } pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat { - ast_from_text(&format!("fn f({} {}: ()))", path, fields)) + ast_from_text(&format!("fn f({path} {fields}: ()))")) } pub fn record_pat_field_list( fields: impl IntoIterator<Item = ast::RecordPatField>, ) -> ast::RecordPatFieldList { let fields = fields.into_iter().join(", "); - ast_from_text(&format!("fn f(S {{ {} }}: ()))", fields)) + ast_from_text(&format!("fn f(S {{ {fields} }}: ()))")) } pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField { - ast_from_text(&format!("fn f(S {{ {}: {} }}: ()))", name_ref, pat)) + ast_from_text(&format!("fn f(S {{ {name_ref}: {pat} }}: ()))")) } pub fn record_pat_field_shorthand(name_ref: ast::NameRef) -> ast::RecordPatField { - ast_from_text(&format!("fn f(S {{ {} }}: ()))", name_ref)) + ast_from_text(&format!("fn f(S {{ {name_ref} }}: ()))")) } /// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. pub fn path_pat(path: ast::Path) -> ast::Pat { return from_text(&path.to_string()); fn from_text(text: &str) -> ast::Pat { - ast_from_text(&format!("fn f({}: ())", text)) + ast_from_text(&format!("fn f({text}: ())")) } } @@ -577,12 +580,12 @@ pub fn match_arm( ) -> ast::MatchArm { let pats_str = pats.into_iter().join(" | "); return match guard { - Some(guard) => from_text(&format!("{} if {} => {}", pats_str, guard, expr)), - None => from_text(&format!("{} => {}", pats_str, expr)), + Some(guard) => from_text(&format!("{pats_str} if {guard} => {expr}")), + None => from_text(&format!("{pats_str} => {expr}")), }; fn from_text(text: &str) -> ast::MatchArm { - ast_from_text(&format!("fn f() {{ match () {{{}}} }}", text)) + ast_from_text(&format!("fn f() {{ match () {{{text}}} }}")) } } @@ -592,10 +595,10 @@ pub fn match_arm_with_guard( expr: ast::Expr, ) -> ast::MatchArm { let pats_str = pats.into_iter().join(" | "); - return from_text(&format!("{} if {} => {}", pats_str, guard, expr)); + return from_text(&format!("{pats_str} if {guard} => {expr}")); fn from_text(text: &str) -> ast::MatchArm { - ast_from_text(&format!("fn f() {{ match () {{{}}} }}", text)) + ast_from_text(&format!("fn f() {{ match () {{{text}}} }}")) } } @@ -605,13 +608,14 @@ pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::Mat .map(|arm| { let needs_comma = arm.expr().map_or(true, |it| !it.is_block_like()); let comma = if needs_comma { "," } else { "" }; - format!(" {}{}\n", arm.syntax(), comma) + let arm = arm.syntax(); + format!(" {arm}{comma}\n") }) .collect::<String>(); return from_text(&arms_str); fn from_text(text: &str) -> ast::MatchArmList { - ast_from_text(&format!("fn f() {{ match () {{\n{}}} }}", text)) + ast_from_text(&format!("fn f() {{ match () {{\n{text}}} }}")) } } @@ -620,10 +624,10 @@ pub fn where_pred( bounds: impl IntoIterator<Item = ast::TypeBound>, ) -> ast::WherePred { let bounds = bounds.into_iter().join(" + "); - return from_text(&format!("{}: {}", path, bounds)); + return from_text(&format!("{path}: {bounds}")); fn from_text(text: &str) -> ast::WherePred { - ast_from_text(&format!("fn f() where {} {{ }}", text)) + ast_from_text(&format!("fn f() where {text} {{ }}")) } } @@ -632,7 +636,7 @@ pub fn where_clause(preds: impl IntoIterator<Item = ast::WherePred>) -> ast::Whe return from_text(preds.as_str()); fn from_text(text: &str) -> ast::WhereClause { - ast_from_text(&format!("fn f() where {} {{ }}", text)) + ast_from_text(&format!("fn f() where {text} {{ }}")) } } @@ -642,19 +646,19 @@ pub fn let_stmt( initializer: Option<ast::Expr>, ) -> ast::LetStmt { let mut text = String::new(); - format_to!(text, "let {}", pattern); + format_to!(text, "let {pattern}"); if let Some(ty) = ty { - format_to!(text, ": {}", ty); + format_to!(text, ": {ty}"); } match initializer { - Some(it) => format_to!(text, " = {};", it), + Some(it) => format_to!(text, " = {it};"), None => format_to!(text, ";"), }; - ast_from_text(&format!("fn f() {{ {} }}", text)) + ast_from_text(&format!("fn f() {{ {text} }}")) } pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt { let semi = if expr.is_block_like() { "" } else { ";" }; - ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi)) + ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}")) } pub fn item_const( @@ -665,13 +669,13 @@ pub fn item_const( ) -> ast::Const { let visibility = match visibility { None => String::new(), - Some(it) => format!("{} ", it), + Some(it) => format!("{it} "), }; - ast_from_text(&format!("{} const {}: {} = {};", visibility, name, ty, expr)) + ast_from_text(&format!("{visibility} const {name}: {ty} = {expr};")) } pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param { - ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty)) + ast_from_text(&format!("fn f({pat}: {ty}) {{ }}")) } pub fn self_param() -> ast::SelfParam { @@ -679,7 +683,7 @@ pub fn self_param() -> ast::SelfParam { } pub fn ret_type(ty: ast::Type) -> ast::RetType { - ast_from_text(&format!("fn f() -> {} {{ }}", ty)) + ast_from_text(&format!("fn f() -> {ty} {{ }}")) } pub fn param_list( @@ -688,30 +692,30 @@ pub fn param_list( ) -> ast::ParamList { let args = pats.into_iter().join(", "); let list = match self_param { - Some(self_param) if args.is_empty() => format!("fn f({}) {{ }}", self_param), - Some(self_param) => format!("fn f({}, {}) {{ }}", self_param, args), - None => format!("fn f({}) {{ }}", args), + Some(self_param) if args.is_empty() => format!("fn f({self_param}) {{ }}"), + Some(self_param) => format!("fn f({self_param}, {args}) {{ }}"), + None => format!("fn f({args}) {{ }}"), }; ast_from_text(&list) } pub fn type_param(name: ast::Name, ty: Option<ast::TypeBoundList>) -> ast::TypeParam { let bound = match ty { - Some(it) => format!(": {}", it), + Some(it) => format!(": {it}"), None => String::new(), }; - ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound)) + ast_from_text(&format!("fn f<{name}{bound}>() {{ }}")) } pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam { - ast_from_text(&format!("fn f<{}>() {{ }}", lifetime)) + ast_from_text(&format!("fn f<{lifetime}>() {{ }}")) } pub fn generic_param_list( pats: impl IntoIterator<Item = ast::GenericParam>, ) -> ast::GenericParamList { let args = pats.into_iter().join(", "); - ast_from_text(&format!("fn f<{}>() {{ }}", args)) + ast_from_text(&format!("fn f<{args}>() {{ }}")) } pub fn visibility_pub_crate() -> ast::Visibility { @@ -724,33 +728,33 @@ pub fn visibility_pub() -> ast::Visibility { pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> ast::TupleFieldList { let fields = fields.into_iter().join(", "); - ast_from_text(&format!("struct f({});", fields)) + ast_from_text(&format!("struct f({fields});")) } pub fn record_field_list( fields: impl IntoIterator<Item = ast::RecordField>, ) -> ast::RecordFieldList { let fields = fields.into_iter().join(", "); - ast_from_text(&format!("struct f {{ {} }}", fields)) + ast_from_text(&format!("struct f {{ {fields} }}")) } pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField { let visibility = match visibility { None => String::new(), - Some(it) => format!("{} ", it), + Some(it) => format!("{it} "), }; - ast_from_text(&format!("struct f({}{});", visibility, ty)) + ast_from_text(&format!("struct f({visibility}{ty});")) } pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant { let field_list = match field_list { None => String::new(), Some(it) => match it { - ast::FieldList::RecordFieldList(record) => format!(" {}", record), - ast::FieldList::TupleFieldList(tuple) => format!("{}", tuple), + ast::FieldList::RecordFieldList(record) => format!(" {record}"), + ast::FieldList::TupleFieldList(tuple) => format!("{tuple}"), }, }; - ast_from_text(&format!("enum f {{ {}{} }}", name, field_list)) + ast_from_text(&format!("enum f {{ {name}{field_list} }}")) } pub fn fn_( @@ -763,23 +767,22 @@ pub fn fn_( is_async: bool, ) -> ast::Fn { let type_params = match type_params { - Some(type_params) => format!("{}", type_params), + Some(type_params) => format!("{type_params}"), None => "".into(), }; let ret_type = match ret_type { - Some(ret_type) => format!("{} ", ret_type), + Some(ret_type) => format!("{ret_type} "), None => "".into(), }; let visibility = match visibility { None => String::new(), - Some(it) => format!("{} ", it), + Some(it) => format!("{it} "), }; let async_literal = if is_async { "async " } else { "" }; ast_from_text(&format!( - "{}{}fn {}{}{} {}{}", - visibility, async_literal, fn_name, type_params, params, ret_type, body + "{visibility}{async_literal}fn {fn_name}{type_params}{params} {ret_type}{body}", )) } @@ -793,13 +796,10 @@ pub fn struct_( let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string()); let visibility = match visibility { None => String::new(), - Some(it) => format!("{} ", it), + Some(it) => format!("{it} "), }; - ast_from_text(&format!( - "{}struct {}{}{}{}", - visibility, strukt_name, type_params, field_list, semicolon - )) + ast_from_text(&format!("{visibility}struct {strukt_name}{type_params}{field_list}{semicolon}",)) } #[track_caller] @@ -808,7 +808,8 @@ fn ast_from_text<N: AstNode>(text: &str) -> N { let node = match parse.tree().syntax().descendants().find_map(N::cast) { Some(it) => it, None => { - panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text) + let node = std::any::type_name::<N>(); + panic!("Failed to make ast node `{node}` from text {text}") } }; let node = node.clone_subtree(); @@ -824,7 +825,7 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken { .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == kind) - .unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) + .unwrap_or_else(|| panic!("unhandled token: {kind:?}")) } pub mod tokens { @@ -863,7 +864,7 @@ pub mod tokens { pub fn literal(text: &str) -> SyntaxToken { assert_eq!(text.trim(), text); - let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {}; }}", text)); + let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {text}; }}")); lit.syntax().first_child_or_token().unwrap().into_token().unwrap() } diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml index 9ee4415dcad..fcc693a7dda 100644 --- a/crates/vfs-notify/Cargo.toml +++ b/crates/vfs-notify/Cargo.toml @@ -14,7 +14,7 @@ tracing = "0.1.35" jod-thread = "0.1.2" walkdir = "2.3.2" crossbeam-channel = "0.5.5" -notify = "=5.0.0-pre.15" +notify = "=5.0.0-pre.16" vfs = { path = "../vfs", version = "0.0.0" } paths = { path = "../paths", version = "0.0.0" } diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs index d6d9c66159f..c95304e55ac 100644 --- a/crates/vfs-notify/src/lib.rs +++ b/crates/vfs-notify/src/lib.rs @@ -12,7 +12,7 @@ use std::fs; use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; -use notify::{RecommendedWatcher, RecursiveMode, Watcher}; +use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; use paths::{AbsPath, AbsPathBuf}; use vfs::loader; use walkdir::WalkDir; @@ -91,9 +91,12 @@ impl NotifyActor { self.watcher = None; if !config.watch.is_empty() { let (watcher_sender, watcher_receiver) = unbounded(); - let watcher = log_notify_error(RecommendedWatcher::new(move |event| { - watcher_sender.send(event).unwrap(); - })); + let watcher = log_notify_error(RecommendedWatcher::new( + move |event| { + watcher_sender.send(event).unwrap(); + }, + Config::default(), + )); self.watcher = watcher.map(|it| (it, watcher_receiver)); } diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml index c6377348784..d7549a28415 100644 --- a/crates/vfs/Cargo.toml +++ b/crates/vfs/Cargo.toml @@ -12,6 +12,7 @@ doctest = false [dependencies] rustc-hash = "1.1.0" fst = "0.4.7" +indexmap = "1.9.1" paths = { path = "../paths", version = "0.0.0" } -indexmap = "1.9.1" +stdx = { path = "../stdx", version = "0.0.0" } diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index 6a89263e539..e0ef737b3fc 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs @@ -6,6 +6,7 @@ use std::fmt; use fst::{IntoStreamer, Streamer}; use rustc_hash::FxHashMap; +use stdx::hash::NoHashHashMap; use crate::{AnchoredPath, FileId, Vfs, VfsPath}; @@ -13,7 +14,7 @@ use crate::{AnchoredPath, FileId, Vfs, VfsPath}; #[derive(Default, Clone, Eq, PartialEq)] pub struct FileSet { files: FxHashMap<VfsPath, FileId>, - paths: FxHashMap<FileId, VfsPath>, + paths: NoHashHashMap<FileId, VfsPath>, } impl FileSet { diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 7badb1c363b..afc9a0fa6fb 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs @@ -59,9 +59,16 @@ pub use paths::{AbsPath, AbsPathBuf}; /// Handle to a file in [`Vfs`] /// /// Most functions in rust-analyzer use this when they need to refer to a file. -#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct FileId(pub u32); +impl stdx::hash::NoHashHashable for FileId {} +impl std::hash::Hash for FileId { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.0.hash(state); + } +} + /// Storage for all files read by rust-analyzer. /// /// For more information see the [crate-level](crate) documentation. diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 5040643d34a..6d2c7d7b063 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ <!--- -lsp_ext.rs hash: 2a188defec26cc7c +lsp_ext.rs hash: 7b710095d773b978 If you need to change the above hash to make the test pass, please check if you need to adjust this doc as well and ping this issue: diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index e3dbeec1fbd..72b92572647 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -118,6 +118,10 @@ If you're changing this because you're using some tool wrapping Cargo, you might also want to change `#rust-analyzer.cargo.buildScripts.overrideCommand#`. +If there are multiple linked projects, this command is invoked for +each of them, with the working directory being the project root +(i.e., the folder containing the `Cargo.toml`). + An example command would be: ```bash @@ -583,6 +587,52 @@ Enables the use of rustfmt's unstable range formatting command for the `textDocument/rangeFormatting` request. The rustfmt option is unstable and only available on a nightly build. -- +[[rust-analyzer.semanticHighlighting.doc.comment.inject.enable]]rust-analyzer.semanticHighlighting.doc.comment.inject.enable (default: `true`):: ++ +-- +Inject additional highlighting into doc comments. + +When enabled, rust-analyzer will highlight rust source in doc comments as well as intra +doc links. +-- +[[rust-analyzer.semanticHighlighting.operator.enable]]rust-analyzer.semanticHighlighting.operator.enable (default: `true`):: ++ +-- +Use semantic tokens for operators. + +When disabled, rust-analyzer will emit semantic tokens only for operator tokens when +they are tagged with modifiers. +-- +[[rust-analyzer.semanticHighlighting.operator.specialization.enable]]rust-analyzer.semanticHighlighting.operator.specialization.enable (default: `false`):: ++ +-- +Use specialized semantic tokens for operators. + +When enabled, rust-analyzer will emit special token types for operator tokens instead +of the generic `operator` token type. +-- +[[rust-analyzer.semanticHighlighting.punctuation.enable]]rust-analyzer.semanticHighlighting.punctuation.enable (default: `false`):: ++ +-- +Use semantic tokens for punctuations. + +When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when +they are tagged with modifiers or have a special role. +-- +[[rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang]]rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang (default: `false`):: ++ +-- +When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro +calls. +-- +[[rust-analyzer.semanticHighlighting.punctuation.specialization.enable]]rust-analyzer.semanticHighlighting.punctuation.specialization.enable (default: `false`):: ++ +-- +Use specialized semantic tokens for punctuations. + +When enabled, rust-analyzer will emit special token types for punctuation tokens instead +of the generic `punctuation` token type. +-- [[rust-analyzer.semanticHighlighting.strings.enable]]rust-analyzer.semanticHighlighting.strings.enable (default: `true`):: + -- diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index c482fcbed0e..9bd3b6a692b 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -861,3 +861,14 @@ For example, if you want to run https://crates.io/crates/cargo-watch[`cargo watc "isBackground": true } ``` + +==== Live Share + +VS Code Live Share has partial support for rust-analyzer. + +Live Share _requires_ the official Microsoft build of VS Code, OSS builds will not work correctly. + +The host's rust-analyzer instance will be shared with all guests joining the session. +The guests do not have to have the rust-analyzer extension installed for this to work. + +If you are joining a Live Share session and _do_ have rust-analyzer installed locally, commands from the command palette will not work correctly since they will attempt to communicate with the local server. diff --git a/editors/code/package.json b/editors/code/package.json index 0714b356fe0..767c5875bf7 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -235,6 +235,11 @@ "command": "rust-analyzer.moveItemDown", "title": "Move item down", "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.cancelFlycheck", + "title": "Cancel running flychecks", + "category": "rust-analyzer" } ], "keybindings": [ @@ -542,7 +547,7 @@ ] }, "rust-analyzer.checkOnSave.overrideCommand": { - "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefor include `--message-format=json` or a similar option.\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.", + "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefor include `--message-format=json` or a similar option.\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects, this command is invoked for\neach of them, with the working directory being the project root\n(i.e., the folder containing the `Cargo.toml`).\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.", "default": null, "type": [ "null", @@ -1079,6 +1084,36 @@ "default": false, "type": "boolean" }, + "rust-analyzer.semanticHighlighting.doc.comment.inject.enable": { + "markdownDescription": "Inject additional highlighting into doc comments.\n\nWhen enabled, rust-analyzer will highlight rust source in doc comments as well as intra\ndoc links.", + "default": true, + "type": "boolean" + }, + "rust-analyzer.semanticHighlighting.operator.enable": { + "markdownDescription": "Use semantic tokens for operators.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for operator tokens when\nthey are tagged with modifiers.", + "default": true, + "type": "boolean" + }, + "rust-analyzer.semanticHighlighting.operator.specialization.enable": { + "markdownDescription": "Use specialized semantic tokens for operators.\n\nWhen enabled, rust-analyzer will emit special token types for operator tokens instead\nof the generic `operator` token type.", + "default": false, + "type": "boolean" + }, + "rust-analyzer.semanticHighlighting.punctuation.enable": { + "markdownDescription": "Use semantic tokens for punctuations.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when\nthey are tagged with modifiers or have a special role.", + "default": false, + "type": "boolean" + }, + "rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang": { + "markdownDescription": "When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro\ncalls.", + "default": false, + "type": "boolean" + }, + "rust-analyzer.semanticHighlighting.punctuation.specialization.enable": { + "markdownDescription": "Use specialized semantic tokens for punctuations.\n\nWhen enabled, rust-analyzer will emit special token types for punctuation tokens instead\nof the generic `punctuation` token type.", + "default": false, + "type": "boolean" + }, "rust-analyzer.semanticHighlighting.strings.enable": { "markdownDescription": "Use semantic tokens for strings.\n\nIn some editors (e.g. vscode) semantic tokens override other highlighting grammars.\nBy disabling semantic tokens for strings, other grammars can be used to highlight\ntheir contents.", "default": true, diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 27ab31db8db..05d4d08f70b 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -5,7 +5,6 @@ import * as Is from "vscode-languageclient/lib/common/utils/is"; import { assert } from "./util"; import { WorkspaceEdit } from "vscode"; import { Workspace } from "./ctx"; -import { updateConfig } from "./config"; import { substituteVariablesInEnv } from "./config"; import { outputChannel, traceOutputChannel } from "./main"; import { randomUUID } from "crypto"; @@ -86,11 +85,6 @@ export async function createClient( let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); - // Update outdated user configs - await updateConfig(initializationOptions).catch((err) => { - void vscode.window.showErrorMessage(`Failed updating old config keys: ${err.message}`); - }); - if (workspace.kind === "Detached Files") { initializationOptions = { detachedFiles: workspace.files.map((file) => file.uri.fsPath), @@ -105,22 +99,6 @@ export async function createClient( traceOutputChannel: traceOutputChannel(), outputChannel: outputChannel(), middleware: { - async handleDiagnostics(uri, diagnostics, next) { - // Workaround for https://github.com/microsoft/vscode/issues/155531 - for (const diagnostic of diagnostics) { - if (!diagnostic.message) { - diagnostic.message = " "; - } - if (diagnostic.relatedInformation) { - for (const relatedInformation of diagnostic.relatedInformation) { - if (!relatedInformation.message) { - relatedInformation.message = " "; - } - } - } - } - next(uri, diagnostics); - }, async provideHover( document: vscode.TextDocument, position: vscode.Position, diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 12f666401fd..a21b304bbda 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -817,6 +817,12 @@ export function openDocs(ctx: Ctx): Cmd { }; } +export function cancelFlycheck(ctx: Ctx): Cmd { + return async () => { + await ctx.client.sendRequest(ra.cancelFlycheck); + }; +} + export function resolveCodeAction(ctx: Ctx): Cmd { const client = ctx.client; return async (params: lc.CodeAction) => { diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 1c58040d58c..a9c0f079b3d 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -3,10 +3,6 @@ import * as vscode from "vscode"; import { Env } from "./client"; import { log } from "./util"; -export type UpdatesChannel = "stable" | "nightly"; - -const NIGHTLY_TAG = "nightly"; - export type RunnableEnvCfg = | undefined | Record<string, string> @@ -175,104 +171,6 @@ export class Config { gotoTypeDef: this.get<boolean>("hover.actions.gotoTypeDef.enable"), }; } - - get currentExtensionIsNightly() { - return this.package.releaseTag === NIGHTLY_TAG; - } -} - -export async function updateConfig(config: vscode.WorkspaceConfiguration) { - const renames = [ - ["assist.allowMergingIntoGlobImports", "imports.merge.glob"], - ["assist.exprFillDefault", "assist.expressionFillDefault"], - ["assist.importEnforceGranularity", "imports.granularity.enforce"], - ["assist.importGranularity", "imports.granularity.group"], - ["assist.importMergeBehavior", "imports.granularity.group"], - ["assist.importMergeBehaviour", "imports.granularity.group"], - ["assist.importGroup", "imports.group.enable"], - ["assist.importPrefix", "imports.prefix"], - ["primeCaches.enable", "cachePriming.enable"], - ["cache.warmup", "cachePriming.enable"], - ["cargo.loadOutDirsFromCheck", "cargo.buildScripts.enable"], - ["cargo.runBuildScripts", "cargo.buildScripts.enable"], - ["cargo.runBuildScriptsCommand", "cargo.buildScripts.overrideCommand"], - ["cargo.useRustcWrapperForBuildScripts", "cargo.buildScripts.useRustcWrapper"], - ["completion.snippets", "completion.snippets.custom"], - ["diagnostics.enableExperimental", "diagnostics.experimental.enable"], - ["experimental.procAttrMacros", "procMacro.attributes.enable"], - ["highlighting.strings", "semanticHighlighting.strings.enable"], - ["highlightRelated.breakPoints", "highlightRelated.breakPoints.enable"], - ["highlightRelated.exitPoints", "highlightRelated.exitPoints.enable"], - ["highlightRelated.yieldPoints", "highlightRelated.yieldPoints.enable"], - ["highlightRelated.references", "highlightRelated.references.enable"], - ["hover.documentation", "hover.documentation.enable"], - ["hover.linksInHover", "hover.links.enable"], - ["hoverActions.linksInHover", "hover.links.enable"], - ["hoverActions.debug", "hover.actions.debug.enable"], - ["hoverActions.enable", "hover.actions.enable.enable"], - ["hoverActions.gotoTypeDef", "hover.actions.gotoTypeDef.enable"], - ["hoverActions.implementations", "hover.actions.implementations.enable"], - ["hoverActions.references", "hover.actions.references.enable"], - ["hoverActions.run", "hover.actions.run.enable"], - ["inlayHints.chainingHints", "inlayHints.chainingHints.enable"], - ["inlayHints.closureReturnTypeHints", "inlayHints.closureReturnTypeHints.enable"], - ["inlayHints.hideNamedConstructorHints", "inlayHints.typeHints.hideNamedConstructor"], - ["inlayHints.parameterHints", "inlayHints.parameterHints.enable"], - ["inlayHints.reborrowHints", "inlayHints.reborrowHints.enable"], - ["inlayHints.typeHints", "inlayHints.typeHints.enable"], - ["lruCapacity", "lru.capacity"], - ["runnables.cargoExtraArgs", "runnables.extraArgs"], - ["runnables.overrideCargo", "runnables.command"], - ["rustcSource", "rustc.source"], - ["rustfmt.enableRangeFormatting", "rustfmt.rangeFormatting.enable"], - ]; - - for (const [oldKey, newKey] of renames) { - const inspect = config.inspect(oldKey); - if (inspect !== undefined) { - const valMatrix = [ - { - val: inspect.globalValue, - langVal: inspect.globalLanguageValue, - target: vscode.ConfigurationTarget.Global, - }, - { - val: inspect.workspaceFolderValue, - langVal: inspect.workspaceFolderLanguageValue, - target: vscode.ConfigurationTarget.WorkspaceFolder, - }, - { - val: inspect.workspaceValue, - langVal: inspect.workspaceLanguageValue, - target: vscode.ConfigurationTarget.Workspace, - }, - ]; - for (const { val, langVal, target } of valMatrix) { - const patch = (val: unknown) => { - // some of the updates we do only append "enable" or "custom" - // that means on the next run we would find these again, but as objects with - // these properties causing us to destroy the config - // so filter those already updated ones out - return ( - val !== undefined && - !( - typeof val === "object" && - val !== null && - (oldKey === "completion.snippets" || !val.hasOwnProperty("custom")) - ) - ); - }; - if (patch(val)) { - await config.update(newKey, val, target, false); - await config.update(oldKey, undefined, target, false); - } - if (patch(langVal)) { - await config.update(newKey, langVal, target, true); - await config.update(oldKey, undefined, target, true); - } - } - } - } } export function substituteVariablesInEnv(env: Env): Env { diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index f80af78a74a..875261c48a6 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -75,6 +75,23 @@ export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | "rust-analyzer/expandMacro" ); +export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>( + "rust-analyzer/relatedTests" +); + +export const cancelFlycheck = new lc.RequestType0<void, void>("rust-analyzer/cancelFlycheck"); + +// Experimental extensions + +export interface SsrParams { + query: string; + parseOnly: boolean; + textDocument: lc.TextDocumentIdentifier; + position: lc.Position; + selections: readonly lc.Range[]; +} +export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr"); + export interface MatchingBraceParams { textDocument: lc.TextDocumentIdentifier; positions: lc.Position[]; @@ -127,19 +144,6 @@ export interface TestInfo { runnable: Runnable; } -export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>( - "rust-analyzer/relatedTests" -); - -export interface SsrParams { - query: string; - parseOnly: boolean; - textDocument: lc.TextDocumentIdentifier; - position: lc.Position; - selections: readonly lc.Range[]; -} -export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr"); - export interface CommandLink extends lc.Command { /** * A tooltip for the command, when represented in the UI. diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index d78b711a47a..e9b62e0cc25 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -33,7 +33,7 @@ export function outputChannel() { } export interface RustAnalyzerExtensionApi { - client: lc.LanguageClient; + client?: lc.LanguageClient; } export async function activate( @@ -48,6 +48,23 @@ export async function activate( } async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyzerExtensionApi> { + // We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if + // only those are in use. + // (r-a still somewhat works with Live Share, because commands are tunneled to the host) + const folders = (vscode.workspace.workspaceFolders || []).filter( + (folder) => folder.uri.scheme === "file" + ); + const rustDocuments = vscode.workspace.textDocuments.filter((document) => + isRustDocument(document) + ); + + if (folders.length === 0 && rustDocuments.length === 0) { + // FIXME: Ideally we would choose not to activate at all (and avoid registering + // non-functional editor commands), but VS Code doesn't seem to have a good way of doing + // that + return {}; + } + const config = new Config(context); const state = new PersistentState(context.globalState); const serverPath = await bootstrap(context, config, state).catch((err) => { @@ -60,18 +77,11 @@ async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyz throw new Error(message); }); - if ((vscode.workspace.workspaceFolders || []).length === 0) { - const rustDocuments = vscode.workspace.textDocuments.filter((document) => - isRustDocument(document) - ); - if (rustDocuments.length > 0) { - ctx = await Ctx.create(config, context, serverPath, { - kind: "Detached Files", - files: rustDocuments, - }); - } else { - throw new Error("no rust files are opened"); - } + if (folders.length === 0) { + ctx = await Ctx.create(config, context, serverPath, { + kind: "Detached Files", + files: rustDocuments, + }); } else { // Note: we try to start the server before we activate type hints so that it // registers its `onDidChangeDocument` handler before us. @@ -163,6 +173,7 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) { ctx.registerCommand("peekTests", commands.peekTests); ctx.registerCommand("moveItemUp", commands.moveItemUp); ctx.registerCommand("moveItemDown", commands.moveItemDown); + ctx.registerCommand("cancelFlycheck", commands.cancelFlycheck); defaultOnEnter.dispose(); ctx.registerCommand("onEnter", commands.onEnter); diff --git a/lib/lsp-server/src/socket.rs b/lib/lsp-server/src/socket.rs index 4a59c4c0fad..36d728456f7 100644 --- a/lib/lsp-server/src/socket.rs +++ b/lib/lsp-server/src/socket.rs @@ -15,7 +15,7 @@ pub(crate) fn socket_transport( stream: TcpStream, ) -> (Sender<Message>, Receiver<Message>, IoThreads) { let (reader_receiver, reader) = make_reader(stream.try_clone().unwrap()); - let (writer_sender, writer) = make_write(stream.try_clone().unwrap()); + let (writer_sender, writer) = make_write(stream); let io_threads = make_io_threads(reader, writer); (writer_sender, reader_receiver, io_threads) } |
