diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3f52f31f805d..29ac895493ac 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-16.04, windows-latest, macos-latest] steps: - name: Checkout repository @@ -42,25 +42,25 @@ jobs: override: true - name: Install Nodejs - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-16.04' uses: actions/setup-node@v1 with: node-version: 12.x - name: Dist - if: matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/release' + if: matrix.os == 'ubuntu-16.04' && github.ref == 'refs/heads/release' run: cargo xtask dist --client 0.2.$GITHUB_RUN_NUMBER - name: Dist - if: matrix.os == 'ubuntu-latest' && github.ref != 'refs/heads/release' + if: matrix.os == 'ubuntu-16.04' && github.ref != 'refs/heads/release' run: cargo xtask dist --nightly --client 0.3.$GITHUB_RUN_NUMBER-nightly - name: Dist - if: matrix.os != 'ubuntu-latest' + if: matrix.os != 'ubuntu-16.04' run: cargo xtask dist - name: Nightly analysis-stats check - if: matrix.os == 'ubuntu-latest' && github.ref != 'refs/heads/release' + if: matrix.os == 'ubuntu-16.04' && github.ref != 'refs/heads/release' run: ./dist/rust-analyzer-linux analysis-stats . - name: Upload artifacts @@ -71,7 +71,7 @@ jobs: publish: name: publish - runs-on: ubuntu-latest + runs-on: ubuntu-16.04 needs: ['dist'] steps: - name: Install Nodejs @@ -94,7 +94,7 @@ jobs: path: dist - uses: actions/download-artifact@v1 with: - name: dist-ubuntu-latest + name: dist-ubuntu-16.04 path: dist - uses: actions/download-artifact@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 85ea4f178f58..c062366923dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" +dependencies = [ + "gimli", +] + [[package]] name = "aho-corasick" version = "0.7.10" @@ -11,9 +20,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.28" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" [[package]] name = "anymap" @@ -46,31 +55,22 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.46" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" dependencies = [ - "backtrace-sys", + "addr2line", "cfg-if", "libc", + "object", "rustc-demangle", ] -[[package]] -name = "backtrace-sys" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fbebbe1c9d1f383a9cc7e8ccdb471b91c8d024ee9c2ca5b5346121fe8b4399" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" +checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" [[package]] name = "bitflags" @@ -80,18 +80,18 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bstr" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" dependencies = [ "memchr", ] [[package]] name = "cargo_metadata" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202" +checksum = "b8de60b887edf6d74370fc8eb177040da4847d971d6234c7b13a6da324ef0caf" dependencies = [ "semver", "serde", @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" +checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" [[package]] name = "cfg-if" @@ -113,8 +113,8 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chalk-derive" -version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git?rev=2c072cc830d04af5f10b390e6643327f85108282#2c072cc830d04af5f10b390e6643327f85108282" +version = "0.10.1-dev" +source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" dependencies = [ "proc-macro2", "quote", @@ -124,8 +124,8 @@ dependencies = [ [[package]] name = "chalk-engine" -version = "0.9.0" -source = "git+https://github.com/rust-lang/chalk.git?rev=2c072cc830d04af5f10b390e6643327f85108282#2c072cc830d04af5f10b390e6643327f85108282" +version = "0.10.1-dev" +source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" dependencies = [ "chalk-macros", "rustc-hash", @@ -133,8 +133,8 @@ dependencies = [ [[package]] name = "chalk-ir" -version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git?rev=2c072cc830d04af5f10b390e6643327f85108282#2c072cc830d04af5f10b390e6643327f85108282" +version = "0.10.1-dev" +source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" dependencies = [ "chalk-derive", "chalk-engine", @@ -143,16 +143,16 @@ dependencies = [ [[package]] name = "chalk-macros" -version = "0.1.1" -source = "git+https://github.com/rust-lang/chalk.git?rev=2c072cc830d04af5f10b390e6643327f85108282#2c072cc830d04af5f10b390e6643327f85108282" +version = "0.10.1-dev" +source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" dependencies = [ "lazy_static", ] [[package]] name = "chalk-rust-ir" -version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git?rev=2c072cc830d04af5f10b390e6643327f85108282#2c072cc830d04af5f10b390e6643327f85108282" +version = "0.10.1-dev" +source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" dependencies = [ "chalk-derive", "chalk-engine", @@ -162,8 +162,8 @@ dependencies = [ [[package]] name = "chalk-solve" -version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git?rev=2c072cc830d04af5f10b390e6643327f85108282#2c072cc830d04af5f10b390e6643327f85108282" +version = "0.10.1-dev" +source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" dependencies = [ "chalk-derive", "chalk-engine", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" +checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" dependencies = [ "cfg-if", "libc", @@ -360,9 +360,9 @@ checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fs_extra" @@ -422,6 +422,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" + [[package]] name = "globset" version = "0.4.5" @@ -437,9 +443,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd5e3132801a1ac34ac53b97acde50c4685414dd2f291b9ea52afa6f07468c8" +checksum = "d20fd25aa456527ce4f544271ae4fea65d2eda4a6561ea56f39fb3ee4f7e3884" dependencies = [ "log", "plain", @@ -457,13 +463,22 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ "libc", ] +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "idna" version = "0.2.0" @@ -604,24 +619,24 @@ checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" [[package]] name = "libloading" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c4f51b790f5bdb65acb4cc94bb81d7b2ee60348a5431ac1467d390b017600b0" +checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" dependencies = [ "winapi 0.3.8", ] [[package]] name = "linked-hash-map" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "lock_api" @@ -655,9 +670,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.74.0" +version = "0.74.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "820f746e5716ab9a2d664794636188bd003023b72e55404ee27105dc22869922" +checksum = "57c0e6a2b8837d27b29deb3f3e6dc1c6d2f57947677f9be1024e482ec5b59525" dependencies = [ "base64", "bitflags", @@ -706,9 +721,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ "cfg-if", "fuchsia-zircon", @@ -749,9 +764,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ "cfg-if", "libc", @@ -787,10 +802,16 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.3.1" +name = "object" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "ordermap" @@ -824,9 +845,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c897744f63f34f7ae3a024d9162bb5001f4ad661dd24bea0dc9f075d2de1c6" +checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" dependencies = [ "paste-impl", "proc-macro-hack", @@ -834,9 +855,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fd6f92e3594f2dd7b3fc23e42d82e292f7bcda6d8e5dcd167072327234ab89" +checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -874,9 +895,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "proc-macro-hack" @@ -886,18 +907,18 @@ checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ "proc-macro2", ] @@ -954,10 +975,9 @@ version = "0.1.0" dependencies = [ "cargo_metadata", "crossbeam-channel", - "insta", "jod-thread", "log", - "lsp-types", + "ra_toolchain", "serde_json", ] @@ -1163,6 +1183,7 @@ dependencies = [ "ra_cfg", "ra_db", "ra_proc_macro", + "ra_toolchain", "rustc-hash", "serde", "serde_json", @@ -1194,6 +1215,13 @@ dependencies = [ "text-size", ] +[[package]] +name = "ra_toolchain" +version = "0.1.0" +dependencies = [ + "home", +] + [[package]] name = "ra_tt" version = "0.1.0" @@ -1203,9 +1231,9 @@ dependencies = [ [[package]] name = "ra_vfs" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaa5615f420134aea7667253db101d03a5c5f300eac607872dc2a36407b2ac9" +checksum = "cbf31a173fc77ec59c27cf39af6baa137b40f4dbd45a8b3eccb1b2e4cfc922c1" dependencies = [ "crossbeam-channel", "jod-thread", @@ -1351,6 +1379,7 @@ dependencies = [ "crossbeam-channel", "env_logger", "globset", + "insta", "itertools", "jod-thread", "log", @@ -1469,9 +1498,9 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28" +checksum = "e367622f934864ffa1c704ba2b82280aab856e3d8213c84c5720257eb34b15b9" dependencies = [ "proc-macro2", "quote", @@ -1496,18 +1525,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2", "quote", @@ -1516,9 +1545,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "itoa", "ryu", @@ -1538,9 +1567,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" +checksum = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4" dependencies = [ "dtoa", "linked-hash-map", @@ -1581,9 +1610,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" [[package]] name = "syn" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" +checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" dependencies = [ "proc-macro2", "quote", @@ -1667,9 +1696,9 @@ dependencies = [ [[package]] name = "threadpool" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dae184447c15d5a6916d973c642aec485105a13cd238192a6927ae3e077d66" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ "num_cpus", ] diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs new file mode 100644 index 000000000000..c0a0226fb247 --- /dev/null +++ b/crates/ra_assists/src/assist_config.rs @@ -0,0 +1,27 @@ +//! Settings for tweaking assists. +//! +//! The fun thing here is `SnippetCap` -- this type can only be created in this +//! module, and we use to statically check that we only produce snippet +//! assists if we are allowed to. + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AssistConfig { + pub snippet_cap: Option, +} + +impl AssistConfig { + pub fn allow_snippets(&mut self, yes: bool) { + self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SnippetCap { + _private: (), +} + +impl Default for AssistConfig { + fn default() -> Self { + AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } + } +} diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs new file mode 100644 index 000000000000..f3af70a3ec9e --- /dev/null +++ b/crates/ra_assists/src/assist_context.rs @@ -0,0 +1,257 @@ +//! See `AssistContext` + +use algo::find_covering_element; +use hir::Semantics; +use ra_db::{FileId, FileRange}; +use ra_fmt::{leading_indent, reindent}; +use ra_ide_db::{ + source_change::{SingleFileChange, SourceChange}, + RootDatabase, +}; +use ra_syntax::{ + algo::{self, find_node_at_offset, SyntaxRewriter}, + AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, + TokenAtOffset, +}; +use ra_text_edit::TextEditBuilder; + +use crate::{ + assist_config::{AssistConfig, SnippetCap}, + Assist, AssistId, GroupLabel, ResolvedAssist, +}; + +/// `AssistContext` allows to apply an assist or check if it could be applied. +/// +/// Assists use a somewhat over-engineered approach, given the current needs. +/// The assists workflow consists of two phases. In the first phase, a user asks +/// for the list of available assists. In the second phase, the user picks a +/// particular assist and it gets applied. +/// +/// There are two peculiarities here: +/// +/// * first, we ideally avoid computing more things then necessary to answer "is +/// assist applicable" in the first phase. +/// * second, when we are applying assist, we don't have a guarantee that there +/// weren't any changes between the point when user asked for assists and when +/// they applied a particular assist. So, when applying assist, we need to do +/// all the checks from scratch. +/// +/// To avoid repeating the same code twice for both "check" and "apply" +/// functions, we use an approach reminiscent of that of Django's function based +/// views dealing with forms. Each assist receives a runtime parameter, +/// `resolve`. It first check if an edit is applicable (potentially computing +/// info required to compute the actual edit). If it is applicable, and +/// `resolve` is `true`, it then computes the actual edit. +/// +/// So, to implement the original assists workflow, we can first apply each edit +/// with `resolve = false`, and then applying the selected edit again, with +/// `resolve = true` this time. +/// +/// Note, however, that we don't actually use such two-phase logic at the +/// moment, because the LSP API is pretty awkward in this place, and it's much +/// easier to just compute the edit eagerly :-) +pub(crate) struct AssistContext<'a> { + pub(crate) config: &'a AssistConfig, + pub(crate) sema: Semantics<'a, RootDatabase>, + pub(crate) db: &'a RootDatabase, + pub(crate) frange: FileRange, + source_file: SourceFile, +} + +impl<'a> AssistContext<'a> { + pub(crate) fn new( + sema: Semantics<'a, RootDatabase>, + config: &'a AssistConfig, + frange: FileRange, + ) -> AssistContext<'a> { + let source_file = sema.parse(frange.file_id); + let db = sema.db; + AssistContext { config, sema, db, frange, source_file } + } + + // NB, this ignores active selection. + pub(crate) fn offset(&self) -> TextSize { + self.frange.range.start() + } + + pub(crate) fn token_at_offset(&self) -> TokenAtOffset { + self.source_file.syntax().token_at_offset(self.offset()) + } + pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option { + self.token_at_offset().find(|it| it.kind() == kind) + } + pub(crate) fn find_node_at_offset(&self) -> Option { + find_node_at_offset(self.source_file.syntax(), self.offset()) + } + pub(crate) fn find_node_at_offset_with_descend(&self) -> Option { + self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) + } + pub(crate) fn covering_element(&self) -> SyntaxElement { + find_covering_element(self.source_file.syntax(), self.frange.range) + } + // FIXME: remove + pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { + find_covering_element(self.source_file.syntax(), range) + } +} + +pub(crate) struct Assists { + resolve: bool, + file: FileId, + buf: Vec<(Assist, Option)>, +} + +impl Assists { + pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { + Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } + } + pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { + Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } + } + + pub(crate) fn finish_unresolved(self) -> Vec { + assert!(!self.resolve); + self.finish() + .into_iter() + .map(|(label, edit)| { + assert!(edit.is_none()); + label + }) + .collect() + } + + pub(crate) fn finish_resolved(self) -> Vec { + assert!(self.resolve); + self.finish() + .into_iter() + .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() }) + .collect() + } + + pub(crate) fn add( + &mut self, + id: AssistId, + label: impl Into, + target: TextRange, + f: impl FnOnce(&mut AssistBuilder), + ) -> Option<()> { + let label = Assist::new(id, label.into(), None, target); + self.add_impl(label, f) + } + pub(crate) fn add_group( + &mut self, + group: &GroupLabel, + id: AssistId, + label: impl Into, + target: TextRange, + f: impl FnOnce(&mut AssistBuilder), + ) -> Option<()> { + let label = Assist::new(id, label.into(), Some(group.clone()), target); + self.add_impl(label, f) + } + fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { + let change_label = label.label.clone(); + let source_change = if self.resolve { + let mut builder = AssistBuilder::new(self.file); + f(&mut builder); + Some(builder.finish(change_label)) + } else { + None + }; + + self.buf.push((label, source_change)); + Some(()) + } + + fn finish(mut self) -> Vec<(Assist, Option)> { + self.buf.sort_by_key(|(label, _edit)| label.target.len()); + self.buf + } +} + +pub(crate) struct AssistBuilder { + edit: TextEditBuilder, + file: FileId, + is_snippet: bool, +} + +impl AssistBuilder { + pub(crate) fn new(file: FileId) -> AssistBuilder { + AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } + } + + /// Remove specified `range` of text. + pub(crate) fn delete(&mut self, range: TextRange) { + self.edit.delete(range) + } + /// Append specified `text` at the given `offset` + pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into) { + self.edit.insert(offset, text.into()) + } + /// Append specified `snippet` at the given `offset` + pub(crate) fn insert_snippet( + &mut self, + _cap: SnippetCap, + offset: TextSize, + snippet: impl Into, + ) { + self.is_snippet = true; + self.insert(offset, snippet); + } + /// Replaces specified `range` of text with a given string. + pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into) { + self.edit.replace(range, replace_with.into()) + } + /// Replaces specified `range` of text with a given `snippet`. + pub(crate) fn replace_snippet( + &mut self, + _cap: SnippetCap, + range: TextRange, + snippet: impl Into, + ) { + self.is_snippet = true; + self.replace(range, snippet); + } + pub(crate) fn replace_ast(&mut self, old: N, new: N) { + algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) + } + /// Replaces specified `node` of text with a given string, reindenting the + /// string to maintain `node`'s existing indent. + // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent + pub(crate) fn replace_node_and_indent( + &mut self, + node: &SyntaxNode, + replace_with: impl Into, + ) { + let mut replace_with = replace_with.into(); + if let Some(indent) = leading_indent(node) { + replace_with = reindent(&replace_with, &indent) + } + self.replace(node.text_range(), replace_with) + } + pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { + let node = rewriter.rewrite_root().unwrap(); + let new = rewriter.rewrite(&node); + algo::diff(&node, &new).into_text_edit(&mut self.edit) + } + + // FIXME: better API + pub(crate) fn set_file(&mut self, assist_file: FileId) { + self.file = assist_file; + } + + // FIXME: kill this API + /// Get access to the raw `TextEditBuilder`. + pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { + &mut self.edit + } + + fn finish(self, change_label: String) -> SourceChange { + let edit = self.edit.finish(); + let mut res = SingleFileChange { label: change_label, edit }.into_source_change(self.file); + if self.is_snippet { + res.is_snippet = true; + } + res + } +} diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs deleted file mode 100644 index 2fe7c3de3d14..000000000000 --- a/crates/ra_assists/src/assist_ctx.rs +++ /dev/null @@ -1,257 +0,0 @@ -//! This module defines `AssistCtx` -- the API surface that is exposed to assists. -use hir::Semantics; -use ra_db::FileRange; -use ra_fmt::{leading_indent, reindent}; -use ra_ide_db::RootDatabase; -use ra_syntax::{ - algo::{self, find_covering_element, find_node_at_offset}, - AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, - TokenAtOffset, -}; -use ra_text_edit::TextEditBuilder; - -use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; -use algo::SyntaxRewriter; - -#[derive(Clone, Debug)] -pub(crate) struct Assist(pub(crate) Vec); - -#[derive(Clone, Debug)] -pub(crate) struct AssistInfo { - pub(crate) label: AssistLabel, - pub(crate) group_label: Option, - pub(crate) action: Option, -} - -impl AssistInfo { - fn new(label: AssistLabel) -> AssistInfo { - AssistInfo { label, group_label: None, action: None } - } - - fn resolved(self, action: AssistAction) -> AssistInfo { - AssistInfo { action: Some(action), ..self } - } - - fn with_group(self, group_label: GroupLabel) -> AssistInfo { - AssistInfo { group_label: Some(group_label), ..self } - } - - pub(crate) fn into_resolved(self) -> Option { - let label = self.label; - let group_label = self.group_label; - self.action.map(|action| ResolvedAssist { label, group_label, action }) - } -} - -pub(crate) type AssistHandler = fn(AssistCtx) -> Option; - -/// `AssistCtx` allows to apply an assist or check if it could be applied. -/// -/// Assists use a somewhat over-engineered approach, given the current needs. The -/// assists workflow consists of two phases. In the first phase, a user asks for -/// the list of available assists. In the second phase, the user picks a -/// particular assist and it gets applied. -/// -/// There are two peculiarities here: -/// -/// * first, we ideally avoid computing more things then necessary to answer -/// "is assist applicable" in the first phase. -/// * second, when we are applying assist, we don't have a guarantee that there -/// weren't any changes between the point when user asked for assists and when -/// they applied a particular assist. So, when applying assist, we need to do -/// all the checks from scratch. -/// -/// To avoid repeating the same code twice for both "check" and "apply" -/// functions, we use an approach reminiscent of that of Django's function based -/// views dealing with forms. Each assist receives a runtime parameter, -/// `should_compute_edit`. It first check if an edit is applicable (potentially -/// computing info required to compute the actual edit). If it is applicable, -/// and `should_compute_edit` is `true`, it then computes the actual edit. -/// -/// So, to implement the original assists workflow, we can first apply each edit -/// with `should_compute_edit = false`, and then applying the selected edit -/// again, with `should_compute_edit = true` this time. -/// -/// Note, however, that we don't actually use such two-phase logic at the -/// moment, because the LSP API is pretty awkward in this place, and it's much -/// easier to just compute the edit eagerly :-) -#[derive(Clone)] -pub(crate) struct AssistCtx<'a> { - pub(crate) sema: &'a Semantics<'a, RootDatabase>, - pub(crate) db: &'a RootDatabase, - pub(crate) frange: FileRange, - source_file: SourceFile, - should_compute_edit: bool, -} - -impl<'a> AssistCtx<'a> { - pub fn new( - sema: &'a Semantics<'a, RootDatabase>, - frange: FileRange, - should_compute_edit: bool, - ) -> AssistCtx<'a> { - let source_file = sema.parse(frange.file_id); - AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit } - } - - pub(crate) fn add_assist( - self, - id: AssistId, - label: impl Into, - f: impl FnOnce(&mut ActionBuilder), - ) -> Option { - let label = AssistLabel::new(label.into(), id); - - let mut info = AssistInfo::new(label); - if self.should_compute_edit { - let action = { - let mut edit = ActionBuilder::default(); - f(&mut edit); - edit.build() - }; - info = info.resolved(action) - }; - - Some(Assist(vec![info])) - } - - pub(crate) fn add_assist_group(self, group_name: impl Into) -> AssistGroup<'a> { - AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() } - } - - pub(crate) fn token_at_offset(&self) -> TokenAtOffset { - self.source_file.syntax().token_at_offset(self.frange.range.start()) - } - - pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option { - self.token_at_offset().find(|it| it.kind() == kind) - } - - pub(crate) fn find_node_at_offset(&self) -> Option { - find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) - } - pub(crate) fn covering_element(&self) -> SyntaxElement { - find_covering_element(self.source_file.syntax(), self.frange.range) - } - pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { - find_covering_element(self.source_file.syntax(), range) - } -} - -pub(crate) struct AssistGroup<'a> { - ctx: AssistCtx<'a>, - group_name: String, - assists: Vec, -} - -impl<'a> AssistGroup<'a> { - pub(crate) fn add_assist( - &mut self, - id: AssistId, - label: impl Into, - f: impl FnOnce(&mut ActionBuilder), - ) { - let label = AssistLabel::new(label.into(), id); - - let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone())); - if self.ctx.should_compute_edit { - let action = { - let mut edit = ActionBuilder::default(); - f(&mut edit); - edit.build() - }; - info = info.resolved(action) - }; - - self.assists.push(info) - } - - pub(crate) fn finish(self) -> Option { - if self.assists.is_empty() { - None - } else { - Some(Assist(self.assists)) - } - } -} - -#[derive(Default)] -pub(crate) struct ActionBuilder { - edit: TextEditBuilder, - cursor_position: Option, - target: Option, - file: AssistFile, -} - -impl ActionBuilder { - /// Replaces specified `range` of text with a given string. - pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into) { - self.edit.replace(range, replace_with.into()) - } - - /// Replaces specified `node` of text with a given string, reindenting the - /// string to maintain `node`'s existing indent. - // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent - pub(crate) fn replace_node_and_indent( - &mut self, - node: &SyntaxNode, - replace_with: impl Into, - ) { - let mut replace_with = replace_with.into(); - if let Some(indent) = leading_indent(node) { - replace_with = reindent(&replace_with, &indent) - } - self.replace(node.text_range(), replace_with) - } - - /// Remove specified `range` of text. - #[allow(unused)] - pub(crate) fn delete(&mut self, range: TextRange) { - self.edit.delete(range) - } - - /// Append specified `text` at the given `offset` - pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into) { - self.edit.insert(offset, text.into()) - } - - /// Specify desired position of the cursor after the assist is applied. - pub(crate) fn set_cursor(&mut self, offset: TextSize) { - self.cursor_position = Some(offset) - } - - /// Specify that the assist should be active withing the `target` range. - /// - /// Target ranges are used to sort assists: the smaller the target range, - /// the more specific assist is, and so it should be sorted first. - pub(crate) fn target(&mut self, target: TextRange) { - self.target = Some(target) - } - - /// Get access to the raw `TextEditBuilder`. - pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { - &mut self.edit - } - - pub(crate) fn replace_ast(&mut self, old: N, new: N) { - algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) - } - pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { - let node = rewriter.rewrite_root().unwrap(); - let new = rewriter.rewrite(&node); - algo::diff(&node, &new).into_text_edit(&mut self.edit) - } - - pub(crate) fn set_file(&mut self, assist_file: AssistFile) { - self.file = assist_file - } - - fn build(self) -> AssistAction { - AssistAction { - edit: self.edit.finish(), - cursor_position: self.cursor_position, - target: self.target, - file: self.file, - } - } -} diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index 9ac65ab397fa..3079a02a2d89 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs @@ -1,7 +1,7 @@ //! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. use rustc_hash::FxHashMap; -use hir::{PathResolution, SemanticsScope}; +use hir::{HirDisplay, PathResolution, SemanticsScope}; use ra_ide_db::RootDatabase; use ra_syntax::{ algo::SyntaxRewriter, @@ -51,7 +51,27 @@ impl<'a> SubstituteTypeParams<'a> { .into_iter() // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky .skip(1) - .zip(substs.into_iter()) + // The actual list of trait type parameters may be longer than the one + // used in the `impl` block due to trailing default type parametrs. + // For that case we extend the `substs` with an empty iterator so we + // can still hit those trailing values and check if they actually have + // a default type. If they do, go for that type from `hir` to `ast` so + // the resulting change can be applied correctly. + .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None))) + .filter_map(|(k, v)| match v { + Some(v) => Some((k, v)), + None => { + let default = k.default(source_scope.db)?; + Some(( + k, + ast::make::type_ref( + &default + .display_source_code(source_scope.db, source_scope.module()?.into()) + .ok()?, + ), + )) + } + }) .collect(); return SubstituteTypeParams { source_scope, diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs deleted file mode 100644 index c0f9bc1fbe75..000000000000 --- a/crates/ra_assists/src/doc_tests.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Each assist definition has a special comment, which specifies docs and -//! example. -//! -//! We collect all the example and write the as tests in this module. - -mod generated; - -use ra_db::FileRange; -use test_utils::{assert_eq_text, extract_range_or_offset}; - -use crate::resolved_assists; - -fn check(assist_id: &str, before: &str, after: &str) { - let (selection, before) = extract_range_or_offset(before); - let (db, file_id) = crate::helpers::with_single_file(&before); - let frange = FileRange { file_id, range: selection.into() }; - - let assist = resolved_assists(&db, frange) - .into_iter() - .find(|assist| assist.label.id.0 == assist_id) - .unwrap_or_else(|| { - panic!( - "\n\nAssist is not applicable: {}\nAvailable assists: {}", - assist_id, - resolved_assists(&db, frange) - .into_iter() - .map(|assist| assist.label.id.0) - .collect::>() - .join(", ") - ) - }); - - let actual = assist.action.edit.apply(&before); - assert_eq_text!(after, &actual); -} diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index 4ea26a55082c..fa70c849684a 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs @@ -6,7 +6,10 @@ use ra_syntax::{ }; use stdx::SepBy; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; // Assist: add_custom_impl // @@ -22,10 +25,10 @@ use crate::{Assist, AssistCtx, AssistId}; // struct S; // // impl Debug for S { -// +// $0 // } // ``` -pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option { +pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let input = ctx.find_node_at_offset::()?; let attr = input.syntax().parent().and_then(ast::Attr::cast)?; @@ -46,11 +49,10 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option { let start_offset = annotated.syntax().parent()?.text_range().end(); let label = - format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); - - ctx.add_assist(AssistId("add_custom_impl"), label, |edit| { - edit.target(attr.syntax().text_range()); + format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); + let target = attr.syntax().text_range(); + acc.add(AssistId("add_custom_impl"), label, target, |builder| { let new_attr_input = input .syntax() .descendants_with_tokens() @@ -61,20 +63,11 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option { let has_more_derives = !new_attr_input.is_empty(); let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); - let mut buf = String::new(); - buf.push_str("\n\nimpl "); - buf.push_str(trait_token.text().as_str()); - buf.push_str(" for "); - buf.push_str(annotated_name.as_str()); - buf.push_str(" {\n"); - - let cursor_delta = if has_more_derives { - let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input); - edit.replace(input.syntax().text_range(), new_attr_input); - delta + if has_more_derives { + builder.replace(input.syntax().text_range(), new_attr_input); } else { let attr_range = attr.syntax().text_range(); - edit.delete(attr_range); + builder.delete(attr_range); let line_break_range = attr .syntax() @@ -82,20 +75,30 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option { .filter(|t| t.kind() == WHITESPACE) .map(|t| t.text_range()) .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); - edit.delete(line_break_range); + builder.delete(line_break_range); + } - attr_range.len() + line_break_range.len() - }; - - edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta); - buf.push_str("\n}"); - edit.insert(start_offset, buf); + match ctx.config.snippet_cap { + Some(cap) => { + builder.insert_snippet( + cap, + start_offset, + format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), + ); + } + None => { + builder.insert( + start_offset, + format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), + ); + } + } }) } #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -115,7 +118,7 @@ struct Foo { } impl Debug for Foo { -<|> + $0 } ", ) @@ -137,7 +140,7 @@ pub struct Foo { } impl Debug for Foo { -<|> + $0 } ", ) @@ -156,7 +159,7 @@ struct Foo {} struct Foo {} impl Debug for Foo { -<|> + $0 } ", ) diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs index 6254eb7c41eb..b123b84988cf 100644 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ b/crates/ra_assists/src/handlers/add_derive.rs @@ -4,7 +4,7 @@ use ra_syntax::{ TextSize, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_derive // @@ -18,31 +18,37 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // -> // ``` -// #[derive()] +// #[derive($0)] // struct Point { // x: u32, // y: u32, // } // ``` -pub(crate) fn add_derive(ctx: AssistCtx) -> Option { +pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let cap = ctx.config.snippet_cap?; let nominal = ctx.find_node_at_offset::()?; let node_start = derive_insertion_offset(&nominal)?; - ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| { + let target = nominal.syntax().text_range(); + acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| { let derive_attr = nominal .attrs() .filter_map(|x| x.as_simple_call()) .filter(|(name, _arg)| name == "derive") .map(|(_name, arg)| arg) .next(); - let offset = match derive_attr { + match derive_attr { None => { - edit.insert(node_start, "#[derive()]\n"); - node_start + TextSize::of("#[derive(") + builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); + } + Some(tt) => { + // Just move the cursor. + builder.insert_snippet( + cap, + tt.syntax().text_range().end() - TextSize::of(')'), + "$0", + ) } - Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'), }; - edit.target(nominal.syntax().text_range()); - edit.set_cursor(offset) }) } @@ -57,20 +63,21 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { #[cfg(test)] mod tests { + use crate::tests::{check_assist, check_assist_target}; + use super::*; - use crate::helpers::{check_assist, check_assist_target}; #[test] fn add_derive_new() { check_assist( add_derive, "struct Foo { a: i32, <|>}", - "#[derive(<|>)]\nstruct Foo { a: i32, }", + "#[derive($0)]\nstruct Foo { a: i32, }", ); check_assist( add_derive, "struct Foo { <|> a: i32, }", - "#[derive(<|>)]\nstruct Foo { a: i32, }", + "#[derive($0)]\nstruct Foo { a: i32, }", ); } @@ -79,7 +86,7 @@ mod tests { check_assist( add_derive, "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", - "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", + "#[derive(Clone$0)]\nstruct Foo { a: i32, }", ); } @@ -95,7 +102,7 @@ struct Foo { a: i32<|>, } " /// `Foo` is a pretty important struct. /// It does stuff. -#[derive(<|>)] +#[derive($0)] struct Foo { a: i32, } ", ); diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index bc313782be9c..ab20c66493c1 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs @@ -4,7 +4,7 @@ use ra_syntax::{ TextRange, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_explicit_type // @@ -21,12 +21,12 @@ use crate::{Assist, AssistCtx, AssistId}; // let x: i32 = 92; // } // ``` -pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { +pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let stmt = ctx.find_node_at_offset::()?; + let module = ctx.sema.scope(stmt.syntax()).module()?; let expr = stmt.initializer()?; - let pat = stmt.pat()?; // Must be a binding - let pat = match pat { + let pat = match stmt.pat()? { ast::Pat::BindPat(bind_pat) => bind_pat, _ => return None, }; @@ -45,7 +45,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { // Assist not applicable if the type has already been specified // and it has no placeholders let ascribed_ty = stmt.ascribed_type(); - if let Some(ref ty) = ascribed_ty { + if let Some(ty) = &ascribed_ty { if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { return None; } @@ -57,17 +57,17 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { return None; } - let db = ctx.db; - let new_type_string = ty.display_truncated(db, None).to_string(); - ctx.add_assist( + let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; + acc.add( AssistId("add_explicit_type"), - format!("Insert explicit type '{}'", new_type_string), - |edit| { - edit.target(pat_range); - if let Some(ascribed_ty) = ascribed_ty { - edit.replace(ascribed_ty.syntax().text_range(), new_type_string); - } else { - edit.insert(name_range.end(), format!(": {}", new_type_string)); + format!("Insert explicit type `{}`", inferred_type), + pat_range, + |builder| match ascribed_ty { + Some(ascribed_ty) => { + builder.replace(ascribed_ty.syntax().text_range(), inferred_type); + } + None => { + builder.insert(name_range.end(), format!(": {}", inferred_type)); } }, ) @@ -77,7 +77,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; #[test] fn add_explicit_type_target() { @@ -86,11 +86,7 @@ mod tests { #[test] fn add_explicit_type_works_for_simple_expr() { - check_assist( - add_explicit_type, - "fn f() { let a<|> = 1; }", - "fn f() { let a<|>: i32 = 1; }", - ); + check_assist(add_explicit_type, "fn f() { let a<|> = 1; }", "fn f() { let a: i32 = 1; }"); } #[test] @@ -98,7 +94,7 @@ mod tests { check_assist( add_explicit_type, "fn f() { let a<|>: _ = 1; }", - "fn f() { let a<|>: i32 = 1; }", + "fn f() { let a: i32 = 1; }", ); } @@ -122,7 +118,7 @@ mod tests { } fn f() { - let a<|>: Option = Option::Some(1); + let a: Option = Option::Some(1); }"#, ); } @@ -132,7 +128,7 @@ mod tests { check_assist( add_explicit_type, r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }", - r"macro_rules! v { () => {0u64} } fn f() { let a<|>: u64 = v!(); }", + r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }", ); } @@ -140,8 +136,8 @@ mod tests { fn add_explicit_type_works_for_macro_call_recursive() { check_assist( add_explicit_type, - "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }", - "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|>: u64 = v!(); }", + r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#, + r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#, ); } @@ -208,7 +204,7 @@ struct Test { } fn main() { - let test<|>: Test = Test { t: 23, k: 33 }; + let test: Test = Test { t: 23, k: 33 }; }"#, ); } diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 49deb670175c..6a675e8126dd 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs @@ -1,12 +1,8 @@ use ra_ide_db::RootDatabase; -use ra_syntax::{ - ast::{self, AstNode, NameOwner}, - TextSize, -}; -use stdx::format_to; +use ra_syntax::ast::{self, AstNode, NameOwner}; +use test_utils::mark; -use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; -use test_utils::tested_by; +use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; // Assist add_from_impl_for_enum // @@ -25,7 +21,7 @@ use test_utils::tested_by; // } // } // ``` -pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option { +pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let variant = ctx.find_node_at_offset::()?; let variant_name = variant.name()?; let enum_name = variant.parent_enum().name()?; @@ -38,23 +34,23 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option { } let field_type = field_list.fields().next()?.type_ref()?; let path = match field_type { - ast::TypeRef::PathType(p) => p, + ast::TypeRef::PathType(it) => it, _ => return None, }; - if existing_from_impl(ctx.sema, &variant).is_some() { - tested_by!(test_add_from_impl_already_exists); + if existing_from_impl(&ctx.sema, &variant).is_some() { + mark::hit!(test_add_from_impl_already_exists); return None; } - ctx.add_assist( + let target = variant.syntax().text_range(); + acc.add( AssistId("add_from_impl_for_enum"), "Add From impl for this enum variant", + target, |edit| { let start_offset = variant.parent_enum().syntax().text_range().end(); - let mut buf = String::new(); - format_to!( - buf, + let buf = format!( r#" impl From<{0}> for {1} {{ @@ -67,7 +63,6 @@ impl From<{0}> for {1} {{ variant_name ); edit.insert(start_offset, buf); - edit.set_cursor(start_offset + TextSize::of("\n\n")); }, ) } @@ -95,10 +90,11 @@ fn existing_from_impl( #[cfg(test)] mod tests { - use super::*; + use test_utils::mark; - use crate::helpers::{check_assist, check_assist_not_applicable}; - use test_utils::covers; + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; #[test] fn test_add_from_impl_for_enum() { @@ -107,7 +103,7 @@ mod tests { "enum A { <|>One(u32) }", r#"enum A { One(u32) } -<|>impl From for A { +impl From for A { fn from(v: u32) -> Self { A::One(v) } @@ -119,10 +115,10 @@ mod tests { fn test_add_from_impl_for_enum_complicated_path() { check_assist( add_from_impl_for_enum, - "enum A { <|>One(foo::bar::baz::Boo) }", + r#"enum A { <|>One(foo::bar::baz::Boo) }"#, r#"enum A { One(foo::bar::baz::Boo) } -<|>impl From for A { +impl From for A { fn from(v: foo::bar::baz::Boo) -> Self { A::One(v) } @@ -153,7 +149,7 @@ mod tests { #[test] fn test_add_from_impl_already_exists() { - covers!(test_add_from_impl_already_exists); + mark::check!(test_add_from_impl_already_exists); check_not_applicable( r#" enum A { <|>One(u32), } @@ -184,7 +180,7 @@ pub trait From { }"#, r#"enum A { One(u32), Two(String), } -<|>impl From for A { +impl From for A { fn from(v: u32) -> Self { A::One(v) } diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 6c7456579fe4..24f931a85e19 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs @@ -1,13 +1,21 @@ +use hir::HirDisplay; +use ra_db::FileId; use ra_syntax::{ - ast::{self, AstNode}, + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, ArgListOwner, AstNode, ModuleItemOwner, + }, SyntaxKind, SyntaxNode, TextSize, }; - -use crate::{Assist, AssistCtx, AssistFile, AssistId}; -use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner}; -use hir::HirDisplay; use rustc_hash::{FxHashMap, FxHashSet}; +use crate::{ + assist_config::SnippetCap, + utils::{render_snippet, Cursor}, + AssistContext, AssistId, Assists, +}; + // Assist: add_function // // Adds a stub function with a signature matching the function under the cursor. @@ -29,11 +37,11 @@ use rustc_hash::{FxHashMap, FxHashSet}; // } // // fn bar(arg: &str, baz: Baz) { -// todo!() +// ${0:todo!()} // } // // ``` -pub(crate) fn add_function(ctx: AssistCtx) -> Option { +pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; let path = path_expr.path()?; @@ -43,36 +51,49 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option { return None; } - let target_module = if let Some(qualifier) = path.qualifier() { - if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = - ctx.sema.resolve_path(&qualifier) - { - Some(module.definition_source(ctx.sema.db)) - } else { - return None; - } - } else { - None + let target_module = match path.qualifier() { + Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { + Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), + _ => return None, + }, + None => None, }; let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; - ctx.add_assist(AssistId("add_function"), "Add function", |edit| { - edit.target(call.syntax().text_range()); - - if let Some(function_template) = function_builder.render() { - edit.set_file(function_template.file); - edit.set_cursor(function_template.cursor_offset); - edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); + let target = call.syntax().text_range(); + acc.add(AssistId("add_function"), "Add function", target, |builder| { + let function_template = function_builder.render(); + builder.set_file(function_template.file); + let new_fn = function_template.to_string(ctx.config.snippet_cap); + match ctx.config.snippet_cap { + Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), + None => builder.insert(function_template.insert_offset, new_fn), } }) } struct FunctionTemplate { insert_offset: TextSize, - cursor_offset: TextSize, - fn_def: ast::SourceFile, - file: AssistFile, + placeholder_expr: ast::MacroCall, + leading_ws: String, + fn_def: ast::FnDef, + trailing_ws: String, + file: FileId, +} + +impl FunctionTemplate { + fn to_string(&self, cap: Option) -> String { + let f = match cap { + Some(cap) => render_snippet( + cap, + self.fn_def.syntax(), + Cursor::Replace(self.placeholder_expr.syntax()), + ), + None => self.fn_def.to_string(), + }; + format!("{}{}{}", self.leading_ws, f, self.trailing_ws) + } } struct FunctionBuilder { @@ -80,68 +101,73 @@ struct FunctionBuilder { fn_name: ast::Name, type_params: Option, params: ast::ParamList, - file: AssistFile, + file: FileId, needs_pub: bool, } impl FunctionBuilder { - /// Prepares a generated function that matches `call` in `generate_in` - /// (or as close to `call` as possible, if `generate_in` is `None`) + /// Prepares a generated function that matches `call`. + /// The function is generated in `target_module` or next to `call` fn from_call( - ctx: &AssistCtx, + ctx: &AssistContext, call: &ast::CallExpr, path: &ast::Path, - target_module: Option>, + target_module: Option, ) -> Option { - let needs_pub = target_module.is_some(); - let mut file = AssistFile::default(); - let target = if let Some(target_module) = target_module { - let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; - file = in_file; - target - } else { - next_space_for_fn_after_call_site(&call)? + let mut file = ctx.frange.file_id; + let target = match &target_module { + Some(target_module) => { + let module_source = target_module.definition_source(ctx.db); + let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; + file = in_file; + target + } + None => next_space_for_fn_after_call_site(&call)?, }; + let needs_pub = target_module.is_some(); + let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; let fn_name = fn_name(&path)?; - let (type_params, params) = fn_args(ctx, &call)?; + let (type_params, params) = fn_args(ctx, target_module, &call)?; + Some(Self { target, fn_name, type_params, params, file, needs_pub }) } - fn render(self) -> Option { - let placeholder_expr = ast::make::expr_todo(); - let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); - let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); - if self.needs_pub { - fn_def = ast::make::add_pub_crate_modifier(fn_def); - } + fn render(self) -> FunctionTemplate { + let placeholder_expr = make::expr_todo(); + let fn_body = make::block_expr(vec![], Some(placeholder_expr)); + let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; + let mut fn_def = + make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); + let leading_ws; + let trailing_ws; - let (fn_def, insert_offset) = match self.target { + let insert_offset = match self.target { GeneratedFunctionTarget::BehindItem(it) => { - let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); - let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line); - (indented, it.text_range().end()) + let indent = IndentLevel::from_node(&it); + leading_ws = format!("\n\n{}", indent); + fn_def = fn_def.indent(indent); + trailing_ws = String::new(); + it.text_range().end() } GeneratedFunctionTarget::InEmptyItemList(it) => { - let indent_once = IndentLevel(1); let indent = IndentLevel::from_node(it.syntax()); - - let fn_def = ast::make::add_leading_newlines(1, fn_def); - let fn_def = indent_once.increase_indent(fn_def); - let fn_def = ast::make::add_trailing_newlines(1, fn_def); - let fn_def = indent.increase_indent(fn_def); - (fn_def, it.syntax().text_range().start() + TextSize::of('{')) + leading_ws = format!("\n{}", indent + 1); + fn_def = fn_def.indent(indent + 1); + trailing_ws = format!("\n{}", indent); + it.syntax().text_range().start() + TextSize::of('{') } }; - let cursor_offset_from_fn_start = fn_def - .syntax() - .descendants() - .find_map(ast::MacroCall::cast)? - .syntax() - .text_range() - .start(); - let cursor_offset = insert_offset + cursor_offset_from_fn_start; - Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }) + let placeholder_expr = + fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); + FunctionTemplate { + insert_offset, + placeholder_expr, + leading_ws, + fn_def, + trailing_ws, + file: self.file, + } } } @@ -150,32 +176,41 @@ enum GeneratedFunctionTarget { InEmptyItemList(ast::ItemList), } +impl GeneratedFunctionTarget { + fn syntax(&self) -> &SyntaxNode { + match self { + GeneratedFunctionTarget::BehindItem(it) => it, + GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), + } + } +} + fn fn_name(call: &ast::Path) -> Option { let name = call.segment()?.syntax().to_string(); - Some(ast::make::name(&name)) + Some(make::name(&name)) } /// Computes the type variables and arguments required for the generated function fn fn_args( - ctx: &AssistCtx, + ctx: &AssistContext, + target_module: hir::Module, call: &ast::CallExpr, ) -> Option<(Option, ast::ParamList)> { let mut arg_names = Vec::new(); let mut arg_types = Vec::new(); for arg in call.arg_list()?.args() { - let arg_name = match fn_arg_name(&arg) { + arg_names.push(match fn_arg_name(&arg) { Some(name) => name, None => String::from("arg"), - }; - arg_names.push(arg_name); - arg_types.push(match fn_arg_type(ctx, &arg) { + }); + arg_types.push(match fn_arg_type(ctx, target_module, &arg) { Some(ty) => ty, None => String::from("()"), }); } deduplicate_arg_names(&mut arg_names); - let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); - Some((None, ast::make::param_list(params))) + let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty)); + Some((None, make::param_list(params))) } /// Makes duplicate argument names unique by appending incrementing numbers. @@ -224,12 +259,21 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option { } } -fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option { +fn fn_arg_type( + ctx: &AssistContext, + target_module: hir::Module, + fn_arg: &ast::Expr, +) -> Option { let ty = ctx.sema.type_of_expr(fn_arg)?; if ty.is_unknown() { return None; } - Some(ty.display(ctx.sema.db).to_string()) + + if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { + Some(rendered) + } else { + None + } } /// Returns the position inside the current mod or file @@ -258,11 +302,10 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option, -) -> Option<(AssistFile, GeneratedFunctionTarget)> { - let file = module.file_id.original_file(db); - let assist_file = AssistFile::TargetFile(file); - let assist_item = match module.value { + module_source: &hir::InFile, +) -> Option<(FileId, GeneratedFunctionTarget)> { + let file = module_source.file_id.original_file(db); + let assist_item = match &module_source.value { hir::ModuleSource::SourceFile(it) => { if let Some(last_item) = it.items().last() { GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) @@ -278,12 +321,12 @@ fn next_space_for_fn_in_module( } } }; - Some((assist_file, assist_item)) + Some((file, assist_item)) } #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -302,7 +345,7 @@ fn foo() { } fn bar() { - <|>todo!() + ${0:todo!()} } ", ) @@ -329,7 +372,7 @@ impl Foo { } fn bar() { - <|>todo!() + ${0:todo!()} } ", ) @@ -353,7 +396,7 @@ fn foo1() { } fn bar() { - <|>todo!() + ${0:todo!()} } fn foo2() {} @@ -379,7 +422,7 @@ mod baz { } fn bar() { - <|>todo!() + ${0:todo!()} } } ", @@ -405,7 +448,7 @@ fn foo() { } fn bar(baz: Baz) { - <|>todo!() + ${0:todo!()} } ", ); @@ -438,7 +481,7 @@ impl Baz { } fn bar(baz: Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -459,7 +502,7 @@ fn foo() { } fn bar(arg: &str) { - <|>todo!() + ${0:todo!()} } "#, ) @@ -480,7 +523,7 @@ fn foo() { } fn bar(arg: char) { - <|>todo!() + ${0:todo!()} } "#, ) @@ -501,7 +544,7 @@ fn foo() { } fn bar(arg: i32) { - <|>todo!() + ${0:todo!()} } ", ) @@ -522,7 +565,7 @@ fn foo() { } fn bar(arg: u8) { - <|>todo!() + ${0:todo!()} } ", ) @@ -547,7 +590,7 @@ fn foo() { } fn bar(x: u8) { - <|>todo!() + ${0:todo!()} } ", ) @@ -570,7 +613,7 @@ fn foo() { } fn bar(worble: ()) { - <|>todo!() + ${0:todo!()} } ", ) @@ -599,15 +642,40 @@ fn baz() { } fn bar(foo: impl Foo) { - <|>todo!() + ${0:todo!()} +} +", + ) + } + + #[test] + fn borrowed_arg() { + check_assist( + add_function, + r" +struct Baz; +fn baz() -> Baz { todo!() } + +fn foo() { + bar<|>(&baz()) +} +", + r" +struct Baz; +fn baz() -> Baz { todo!() } + +fn foo() { + bar(&baz()) +} + +fn bar(baz: &Baz) { + ${0:todo!()} } ", ) } #[test] - #[ignore] - // FIXME print paths properly to make this test pass fn add_function_with_qualified_path_arg() { check_assist( add_function, @@ -616,10 +684,8 @@ mod Baz { pub struct Bof; pub fn baz() -> Bof { Bof } } -mod Foo { - fn foo() { - <|>bar(super::Baz::baz()) - } +fn foo() { + <|>bar(Baz::baz()) } ", r" @@ -627,14 +693,12 @@ mod Baz { pub struct Bof; pub fn baz() -> Bof { Bof } } -mod Foo { - fn foo() { - bar(super::Baz::baz()) - } +fn foo() { + bar(Baz::baz()) +} - fn bar(baz: super::Baz::Bof) { - <|>todo!() - } +fn bar(baz: Baz::Bof) { + ${0:todo!()} } ", ) @@ -657,7 +721,7 @@ fn foo(t: T) { } fn bar(t: T) { - <|>todo!() + ${0:todo!()} } ", ) @@ -688,7 +752,7 @@ fn foo() { } fn bar(arg: fn() -> Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -713,7 +777,7 @@ fn foo() { } fn bar(closure: impl Fn(i64) -> i64) { - <|>todo!() + ${0:todo!()} } ", ) @@ -734,7 +798,7 @@ fn foo() { } fn bar(baz: ()) { - <|>todo!() + ${0:todo!()} } ", ) @@ -759,7 +823,7 @@ fn foo() { } fn bar(baz_1: Baz, baz_2: Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -784,7 +848,7 @@ fn foo() { } fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { - <|>todo!() + ${0:todo!()} } "#, ) @@ -804,7 +868,7 @@ fn foo() { r" mod bar { pub(crate) fn my_fn() { - <|>todo!() + ${0:todo!()} } } @@ -815,6 +879,40 @@ fn foo() { ) } + #[test] + #[ignore] + // Ignored until local imports are supported. + // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 + fn qualified_path_uses_correct_scope() { + check_assist( + add_function, + " +mod foo { + pub struct Foo; +} +fn bar() { + use foo::Foo; + let foo = Foo; + baz<|>(foo) +} +", + " +mod foo { + pub struct Foo; +} +fn bar() { + use foo::Foo; + let foo = Foo; + baz(foo) +} + +fn baz(foo: foo::Foo) { + ${0:todo!()} +} +", + ) + } + #[test] fn add_function_in_module_containing_other_items() { check_assist( @@ -833,7 +931,7 @@ mod bar { fn something_else() {} pub(crate) fn my_fn() { - <|>todo!() + ${0:todo!()} } } @@ -861,7 +959,7 @@ fn foo() { mod bar { mod baz { pub(crate) fn my_fn() { - <|>todo!() + ${0:todo!()} } } } @@ -890,7 +988,7 @@ fn main() { pub(crate) fn bar() { - <|>todo!() + ${0:todo!()} }", ) } @@ -926,21 +1024,6 @@ fn bar(baz: ()) {} ) } - #[test] - fn add_function_not_applicable_if_function_path_not_singleton() { - // In the future this assist could be extended to generate functions - // if the path is in the same crate (or even the same workspace). - // For the beginning, I think this is fine. - check_assist_not_applicable( - add_function, - r" -fn foo() { - other_crate::bar<|>(); -} - ", - ) - } - #[test] #[ignore] fn create_method_with_no_args() { diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs index d26f8b93da00..eceba7d0ae67 100644 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ b/crates/ra_assists/src/handlers/add_impl.rs @@ -1,10 +1,7 @@ -use ra_syntax::{ - ast::{self, AstNode, NameOwner, TypeParamsOwner}, - TextSize, -}; +use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; use stdx::{format_to, SepBy}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_impl // @@ -12,24 +9,24 @@ use crate::{Assist, AssistCtx, AssistId}; // // ``` // struct Ctx { -// data: T,<|> +// data: T,<|> // } // ``` // -> // ``` // struct Ctx { -// data: T, +// data: T, // } // // impl Ctx { -// +// $0 // } // ``` -pub(crate) fn add_impl(ctx: AssistCtx) -> Option { +pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let nominal = ctx.find_node_at_offset::()?; let name = nominal.name()?; - ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| { - edit.target(nominal.syntax().text_range()); + let target = nominal.syntax().text_range(); + acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| { let type_params = nominal.type_param_list(); let start_offset = nominal.syntax().text_range().end(); let mut buf = String::new(); @@ -50,30 +47,37 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option { let generic_params = lifetime_params.chain(type_params).sep_by(", "); format_to!(buf, "<{}>", generic_params) } - buf.push_str(" {\n"); - edit.set_cursor(start_offset + TextSize::of(&buf)); - buf.push_str("\n}"); - edit.insert(start_offset, buf); + match ctx.config.snippet_cap { + Some(cap) => { + buf.push_str(" {\n $0\n}"); + edit.insert_snippet(cap, start_offset, buf); + } + None => { + buf.push_str(" {\n}"); + edit.insert(start_offset, buf); + } + } }) } #[cfg(test)] mod tests { + use crate::tests::{check_assist, check_assist_target}; + use super::*; - use crate::helpers::{check_assist, check_assist_target}; #[test] fn test_add_impl() { - check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); + check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n"); check_assist( add_impl, "struct Foo {<|>}", - "struct Foo {}\n\nimpl Foo {\n<|>\n}", + "struct Foo {}\n\nimpl Foo {\n $0\n}", ); check_assist( add_impl, "struct Foo<'a, T: Foo<'a>> {<|>}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", + "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", ); } diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs index 2d6d44980e18..abacd4065fc9 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs @@ -1,13 +1,18 @@ use hir::HasSource; use ra_syntax::{ - ast::{self, edit, make, AstNode, NameOwner}, + ast::{ + self, + edit::{self, AstNodeEdit, IndentLevel}, + make, AstNode, NameOwner, + }, SmolStr, }; use crate::{ + assist_context::{AssistContext, Assists}, ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, - utils::{get_missing_impl_items, resolve_target_trait}, - Assist, AssistCtx, AssistId, + utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, + AssistId, }; #[derive(PartialEq)] @@ -40,12 +45,15 @@ enum AddMissingImplMembersMode { // } // // impl Trait for () { -// fn foo(&self) -> u32 { todo!() } +// fn foo(&self) -> u32 { +// ${0:todo!()} +// } // // } // ``` -pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { +pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { add_missing_impl_members_inner( + acc, ctx, AddMissingImplMembersMode::NoDefaultMethods, "add_impl_missing_members", @@ -81,12 +89,13 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { // impl Trait for () { // Type X = (); // fn foo(&self) {} -// fn bar(&self) {} +// $0fn bar(&self) {} // // } // ``` -pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { +pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { add_missing_impl_members_inner( + acc, ctx, AddMissingImplMembersMode::DefaultMethodsOnly, "add_impl_default_members", @@ -95,36 +104,37 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { } fn add_missing_impl_members_inner( - ctx: AssistCtx, + acc: &mut Assists, + ctx: &AssistContext, mode: AddMissingImplMembersMode, assist_id: &'static str, label: &'static str, -) -> Option { +) -> Option<()> { let _p = ra_prof::profile("add_missing_impl_members_inner"); - let impl_node = ctx.find_node_at_offset::()?; - let impl_item_list = impl_node.item_list()?; + let impl_def = ctx.find_node_at_offset::()?; + let impl_item_list = impl_def.item_list()?; - let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?; + let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; - let def_name = |item: &ast::ImplItem| -> Option { + let def_name = |item: &ast::AssocItem| -> Option { match item { - ast::ImplItem::FnDef(def) => def.name(), - ast::ImplItem::TypeAliasDef(def) => def.name(), - ast::ImplItem::ConstDef(def) => def.name(), + ast::AssocItem::FnDef(def) => def.name(), + ast::AssocItem::TypeAliasDef(def) => def.name(), + ast::AssocItem::ConstDef(def) => def.name(), } .map(|it| it.text().clone()) }; - let missing_items = get_missing_impl_items(&ctx.sema, &impl_node) + let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) .iter() .map(|i| match i { - hir::AssocItem::Function(i) => ast::ImplItem::FnDef(i.source(ctx.db).value), - hir::AssocItem::TypeAlias(i) => ast::ImplItem::TypeAliasDef(i.source(ctx.db).value), - hir::AssocItem::Const(i) => ast::ImplItem::ConstDef(i.source(ctx.db).value), + hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), + hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), + hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), }) .filter(|t| def_name(&t).is_some()) .filter(|t| match t { - ast::ImplItem::FnDef(def) => match mode { + ast::AssocItem::FnDef(def) => match mode { AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), }, @@ -136,44 +146,59 @@ fn add_missing_impl_members_inner( return None; } - let sema = ctx.sema; - - ctx.add_assist(AssistId(assist_id), label, |edit| { - let n_existing_items = impl_item_list.impl_items().count(); - let source_scope = sema.scope_for_def(trait_); - let target_scope = sema.scope(impl_item_list.syntax()); + let target = impl_def.syntax().text_range(); + acc.add(AssistId(assist_id), label, target, |builder| { + let n_existing_items = impl_item_list.assoc_items().count(); + let source_scope = ctx.sema.scope_for_def(trait_); + let target_scope = ctx.sema.scope(impl_item_list.syntax()); let ast_transform = QualifyPaths::new(&target_scope, &source_scope) - .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node)); + .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); let items = missing_items .into_iter() .map(|it| ast_transform::apply(&*ast_transform, it)) .map(|it| match it { - ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), + ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), _ => it, }) .map(|it| edit::remove_attrs_and_docs(&it)); let new_impl_item_list = impl_item_list.append_items(items); - let cursor_position = { - let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); - first_new_item.syntax().text_range().start() - }; + let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); - edit.replace_ast(impl_item_list, new_impl_item_list); - edit.set_cursor(cursor_position); + let original_range = impl_item_list.syntax().text_range(); + match ctx.config.snippet_cap { + None => builder.replace(original_range, new_impl_item_list.to_string()), + Some(cap) => { + let mut cursor = Cursor::Before(first_new_item.syntax()); + let placeholder; + if let ast::AssocItem::FnDef(func) = &first_new_item { + if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { + if m.syntax().text() == "todo!()" { + placeholder = m; + cursor = Cursor::Replace(placeholder.syntax()); + } + } + } + builder.replace_snippet( + cap, + original_range, + render_snippet(cap, new_impl_item_list.syntax(), cursor), + ) + } + }; }) } fn add_body(fn_def: ast::FnDef) -> ast::FnDef { - if fn_def.body().is_none() { - fn_def.with_body(make::block_from_expr(make::expr_todo())) - } else { - fn_def + if fn_def.body().is_some() { + return fn_def; } + let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1)); + fn_def.with_body(body) } #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -181,7 +206,7 @@ mod tests { fn test_add_missing_impl_members() { check_assist( add_missing_impl_members, - " + r#" trait Foo { type Output; @@ -197,8 +222,8 @@ struct S; impl Foo for S { fn bar(&self) {} <|> -}", - " +}"#, + r#" trait Foo { type Output; @@ -213,12 +238,16 @@ struct S; impl Foo for S { fn bar(&self) {} - <|>type Output; + $0type Output; const CONST: usize = 42; - fn foo(&self) { todo!() } - fn baz(&self) { todo!() } + fn foo(&self) { + todo!() + } + fn baz(&self) { + todo!() + } -}", +}"#, ); } @@ -226,7 +255,7 @@ impl Foo for S { fn test_copied_overriden_members() { check_assist( add_missing_impl_members, - " + r#" trait Foo { fn foo(&self); fn bar(&self) -> bool { true } @@ -238,8 +267,8 @@ struct S; impl Foo for S { fn bar(&self) {} <|> -}", - " +}"#, + r#" trait Foo { fn foo(&self); fn bar(&self) -> bool { true } @@ -250,9 +279,11 @@ struct S; impl Foo for S { fn bar(&self) {} - <|>fn foo(&self) { todo!() } + fn foo(&self) { + ${0:todo!()} + } -}", +}"#, ); } @@ -260,16 +291,18 @@ impl Foo for S { fn test_empty_impl_def() { check_assist( add_missing_impl_members, - " + r#" trait Foo { fn foo(&self); } struct S; -impl Foo for S { <|> }", - " +impl Foo for S { <|> }"#, + r#" trait Foo { fn foo(&self); } struct S; impl Foo for S { - <|>fn foo(&self) { todo!() } -}", + fn foo(&self) { + ${0:todo!()} + } +}"#, ); } @@ -277,16 +310,18 @@ impl Foo for S { fn fill_in_type_params_1() { check_assist( add_missing_impl_members, - " + r#" trait Foo { fn foo(&self, t: T) -> &T; } struct S; -impl Foo for S { <|> }", - " +impl Foo for S { <|> }"#, + r#" trait Foo { fn foo(&self, t: T) -> &T; } struct S; impl Foo for S { - <|>fn foo(&self, t: u32) -> &u32 { todo!() } -}", + fn foo(&self, t: u32) -> &u32 { + ${0:todo!()} + } +}"#, ); } @@ -294,16 +329,18 @@ impl Foo for S { fn fill_in_type_params_2() { check_assist( add_missing_impl_members, - " + r#" trait Foo { fn foo(&self, t: T) -> &T; } struct S; -impl Foo for S { <|> }", - " +impl Foo for S { <|> }"#, + r#" trait Foo { fn foo(&self, t: T) -> &T; } struct S; impl Foo for S { - <|>fn foo(&self, t: U) -> &U { todo!() } -}", + fn foo(&self, t: U) -> &U { + ${0:todo!()} + } +}"#, ); } @@ -311,16 +348,18 @@ impl Foo for S { fn test_cursor_after_empty_impl_def() { check_assist( add_missing_impl_members, - " + r#" trait Foo { fn foo(&self); } struct S; -impl Foo for S {}<|>", - " +impl Foo for S {}<|>"#, + r#" trait Foo { fn foo(&self); } struct S; impl Foo for S { - <|>fn foo(&self) { todo!() } -}", + fn foo(&self) { + ${0:todo!()} + } +}"#, ) } @@ -328,22 +367,24 @@ impl Foo for S { fn test_qualify_path_1() { check_assist( add_missing_impl_members, - " + r#" mod foo { pub struct Bar; trait Foo { fn foo(&self, bar: Bar); } } struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { pub struct Bar; trait Foo { fn foo(&self, bar: Bar); } } struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: foo::Bar) { todo!() } -}", + fn foo(&self, bar: foo::Bar) { + ${0:todo!()} + } +}"#, ); } @@ -351,22 +392,24 @@ impl foo::Foo for S { fn test_qualify_path_generic() { check_assist( add_missing_impl_members, - " + r#" mod foo { pub struct Bar; trait Foo { fn foo(&self, bar: Bar); } } struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { pub struct Bar; trait Foo { fn foo(&self, bar: Bar); } } struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: foo::Bar) { todo!() } -}", + fn foo(&self, bar: foo::Bar) { + ${0:todo!()} + } +}"#, ); } @@ -374,22 +417,24 @@ impl foo::Foo for S { fn test_qualify_path_and_substitute_param() { check_assist( add_missing_impl_members, - " + r#" mod foo { pub struct Bar; trait Foo { fn foo(&self, bar: Bar); } } struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { pub struct Bar; trait Foo { fn foo(&self, bar: Bar); } } struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: foo::Bar) { todo!() } -}", + fn foo(&self, bar: foo::Bar) { + ${0:todo!()} + } +}"#, ); } @@ -398,15 +443,15 @@ impl foo::Foo for S { // when substituting params, the substituted param should not be qualified! check_assist( add_missing_impl_members, - " + r#" mod foo { trait Foo { fn foo(&self, bar: T); } pub struct Param; } struct Param; struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { trait Foo { fn foo(&self, bar: T); } pub struct Param; @@ -414,8 +459,10 @@ mod foo { struct Param; struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: Param) { todo!() } -}", + fn foo(&self, bar: Param) { + ${0:todo!()} + } +}"#, ); } @@ -423,15 +470,15 @@ impl foo::Foo for S { fn test_qualify_path_associated_item() { check_assist( add_missing_impl_members, - " + r#" mod foo { pub struct Bar; impl Bar { type Assoc = u32; } trait Foo { fn foo(&self, bar: Bar::Assoc); } } struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { pub struct Bar; impl Bar { type Assoc = u32; } @@ -439,8 +486,10 @@ mod foo { } struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: foo::Bar::Assoc) { todo!() } -}", + fn foo(&self, bar: foo::Bar::Assoc) { + ${0:todo!()} + } +}"#, ); } @@ -448,15 +497,15 @@ impl foo::Foo for S { fn test_qualify_path_nested() { check_assist( add_missing_impl_members, - " + r#" mod foo { pub struct Bar; pub struct Baz; trait Foo { fn foo(&self, bar: Bar); } } struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { pub struct Bar; pub struct Baz; @@ -464,8 +513,10 @@ mod foo { } struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: foo::Bar) { todo!() } -}", + fn foo(&self, bar: foo::Bar) { + ${0:todo!()} + } +}"#, ); } @@ -473,22 +524,24 @@ impl foo::Foo for S { fn test_qualify_path_fn_trait_notation() { check_assist( add_missing_impl_members, - " + r#" mod foo { pub trait Fn { type Output; } trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } } struct S; -impl foo::Foo for S { <|> }", - " +impl foo::Foo for S { <|> }"#, + r#" mod foo { pub trait Fn { type Output; } trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } } struct S; impl foo::Foo for S { - <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { todo!() } -}", + fn foo(&self, bar: dyn Fn(u32) -> i32) { + ${0:todo!()} + } +}"#, ); } @@ -496,10 +549,10 @@ impl foo::Foo for S { fn test_empty_trait() { check_assist_not_applicable( add_missing_impl_members, - " + r#" trait Foo; struct S; -impl Foo for S { <|> }", +impl Foo for S { <|> }"#, ) } @@ -507,13 +560,13 @@ impl Foo for S { <|> }", fn test_ignore_unnamed_trait_members_and_default_methods() { check_assist_not_applicable( add_missing_impl_members, - " + r#" trait Foo { fn (arg: u32); fn valid(some: u32) -> bool { false } } struct S; -impl Foo for S { <|> }", +impl Foo for S { <|> }"#, ) } @@ -543,8 +596,10 @@ trait Foo { } struct S; impl Foo for S { - <|>type Output; - fn foo(&self) { todo!() } + $0type Output; + fn foo(&self) { + todo!() + } }"#, ) } @@ -553,7 +608,7 @@ impl Foo for S { fn test_default_methods() { check_assist( add_missing_default_members, - " + r#" trait Foo { type Output; @@ -563,8 +618,8 @@ trait Foo { fn foo(some: u32) -> bool; } struct S; -impl Foo for S { <|> }", - " +impl Foo for S { <|> }"#, + r#" trait Foo { type Output; @@ -575,8 +630,58 @@ trait Foo { } struct S; impl Foo for S { - <|>fn valid(some: u32) -> bool { false } -}", + $0fn valid(some: u32) -> bool { false } +}"#, + ) + } + + #[test] + fn test_generic_single_default_parameter() { + check_assist( + add_missing_impl_members, + r#" +trait Foo { + fn bar(&self, other: &T); +} + +struct S; +impl Foo for S { <|> }"#, + r#" +trait Foo { + fn bar(&self, other: &T); +} + +struct S; +impl Foo for S { + fn bar(&self, other: &Self) { + ${0:todo!()} + } +}"#, + ) + } + + #[test] + fn test_generic_default_parameter_is_second() { + check_assist( + add_missing_impl_members, + r#" +trait Foo { + fn bar(&self, this: &T1, that: &T2); +} + +struct S; +impl Foo for S { <|> }"#, + r#" +trait Foo { + fn bar(&self, this: &T1, that: &T2); +} + +struct S; +impl Foo for S { + fn bar(&self, this: &T, that: &Self) { + ${0:todo!()} + } +}"#, ) } } diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs index 0f9174a29952..837aa83774ee 100644 --- a/crates/ra_assists/src/handlers/add_new.rs +++ b/crates/ra_assists/src/handlers/add_new.rs @@ -3,11 +3,11 @@ use ra_syntax::{ ast::{ self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, }, - TextSize, T, + T, }; use stdx::{format_to, SepBy}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_new // @@ -25,11 +25,11 @@ use crate::{Assist, AssistCtx, AssistId}; // } // // impl Ctx { -// fn new(data: T) -> Self { Self { data } } +// fn $0new(data: T) -> Self { Self { data } } // } // // ``` -pub(crate) fn add_new(ctx: AssistCtx) -> Option { +pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let strukt = ctx.find_node_at_offset::()?; // We want to only apply this to non-union structs with named fields @@ -41,33 +41,27 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option { // Return early if we've found an existing new fn let impl_def = find_struct_impl(&ctx, &strukt)?; - ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| { - edit.target(strukt.syntax().text_range()); - + let target = strukt.syntax().text_range(); + acc.add(AssistId("add_new"), "Add default constructor", target, |builder| { let mut buf = String::with_capacity(512); if impl_def.is_some() { buf.push('\n'); } - let vis = strukt.visibility().map(|v| format!("{} ", v)); - let vis = vis.as_deref().unwrap_or(""); + let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); let params = field_list .fields() .filter_map(|f| { - Some(format!( - "{}: {}", - f.name()?.syntax().text(), - f.ascribed_type()?.syntax().text() - )) + Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax())) }) .sep_by(", "); let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); - let (start_offset, end_offset) = impl_def + let start_offset = impl_def .and_then(|impl_def| { buf.push('\n'); let start = impl_def @@ -77,17 +71,20 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option { .text_range() .end(); - Some((start, TextSize::of("\n"))) + Some(start) }) .unwrap_or_else(|| { buf = generate_impl_text(&strukt, &buf); - let start = strukt.syntax().text_range().end(); - - (start, TextSize::of("\n}\n")) + strukt.syntax().text_range().end() }); - edit.set_cursor(start_offset + TextSize::of(&buf) - end_offset); - edit.insert(start_offset, buf); + match ctx.config.snippet_cap { + None => builder.insert(start_offset, buf), + Some(cap) => { + buf = buf.replace("fn new", "fn $0new"); + builder.insert_snippet(cap, start_offset, buf); + } + } }) } @@ -124,7 +121,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { // // FIXME: change the new fn checking to a more semantic approach when that's more // viable (e.g. we process proc macros, etc) -fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option> { +fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option> { let db = ctx.db; let module = strukt.syntax().ancestors().find(|node| { ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) @@ -162,8 +159,8 @@ fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option bool { if let Some(il) = imp.item_list() { - for item in il.impl_items() { - if let ast::ImplItem::FnDef(f) = item { + for item in il.assoc_items() { + if let ast::AssocItem::FnDef(f) = item { if let Some(name) = f.name() { if name.text().eq_ignore_ascii_case("new") { return true; @@ -178,7 +175,7 @@ fn has_new_fn(imp: &ast::ImplDef) -> bool { #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use super::*; @@ -192,7 +189,7 @@ mod tests { "struct Foo {} impl Foo { - fn new() -> Self { Self { } }<|> + fn $0new() -> Self { Self { } } } ", ); @@ -202,7 +199,7 @@ impl Foo { "struct Foo {} impl Foo { - fn new() -> Self { Self { } }<|> + fn $0new() -> Self { Self { } } } ", ); @@ -212,7 +209,7 @@ impl Foo { "struct Foo<'a, T: Foo<'a>> {} impl<'a, T: Foo<'a>> Foo<'a, T> { - fn new() -> Self { Self { } }<|> + fn $0new() -> Self { Self { } } } ", ); @@ -222,7 +219,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> { "struct Foo { baz: String } impl Foo { - fn new(baz: String) -> Self { Self { baz } }<|> + fn $0new(baz: String) -> Self { Self { baz } } } ", ); @@ -232,7 +229,7 @@ impl Foo { "struct Foo { baz: String, qux: Vec } impl Foo { - fn new(baz: String, qux: Vec) -> Self { Self { baz, qux } }<|> + fn $0new(baz: String, qux: Vec) -> Self { Self { baz, qux } } } ", ); @@ -244,7 +241,7 @@ impl Foo { "struct Foo { pub baz: String, pub qux: Vec } impl Foo { - fn new(baz: String, qux: Vec) -> Self { Self { baz, qux } }<|> + fn $0new(baz: String, qux: Vec) -> Self { Self { baz, qux } } } ", ); @@ -259,7 +256,7 @@ impl Foo {} "struct Foo {} impl Foo { - fn new() -> Self { Self { } }<|> + fn $0new() -> Self { Self { } } } ", ); @@ -274,7 +271,7 @@ impl Foo { "struct Foo {} impl Foo { - fn new() -> Self { Self { } }<|> + fn $0new() -> Self { Self { } } fn qux(&self) {} } @@ -295,7 +292,7 @@ impl Foo { "struct Foo {} impl Foo { - fn new() -> Self { Self { } }<|> + fn $0new() -> Self { Self { } } fn qux(&self) {} fn baz() -> i32 { @@ -312,7 +309,7 @@ impl Foo { "pub struct Foo {} impl Foo { - pub fn new() -> Self { Self { } }<|> + pub fn $0new() -> Self { Self { } } } ", ); @@ -322,7 +319,7 @@ impl Foo { "pub(crate) struct Foo {} impl Foo { - pub(crate) fn new() -> Self { Self { } }<|> + pub(crate) fn $0new() -> Self { Self { } } } ", ); @@ -415,7 +412,7 @@ pub struct Source { } impl Source { - pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|> + pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } } pub fn map U, U>(self, f: F) -> Source { Source { file_id: self.file_id, ast: f(self.ast) } diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs new file mode 100644 index 000000000000..26acf81f284b --- /dev/null +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs @@ -0,0 +1,134 @@ +use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass}; +use ra_syntax::{ast, AstNode, SyntaxKind, T}; +use test_utils::mark; + +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; + +// Assist: add_turbo_fish +// +// Adds `::<_>` to a call of a generic method or function. +// +// ``` +// fn make() -> T { todo!() } +// fn main() { +// let x = make<|>(); +// } +// ``` +// -> +// ``` +// fn make() -> T { todo!() } +// fn main() { +// let x = make::<${0:_}>(); +// } +// ``` +pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; + let next_token = ident.next_token()?; + if next_token.kind() == T![::] { + mark::hit!(add_turbo_fish_one_fish_is_enough); + return None; + } + let name_ref = ast::NameRef::cast(ident.parent())?; + let def = match classify_name_ref(&ctx.sema, &name_ref)? { + NameRefClass::Definition(def) => def, + NameRefClass::FieldShorthand { .. } => return None, + }; + let fun = match def { + Definition::ModuleDef(hir::ModuleDef::Function(it)) => it, + _ => return None, + }; + let generics = hir::GenericDef::Function(fun).params(ctx.sema.db); + if generics.is_empty() { + mark::hit!(add_turbo_fish_non_generic); + return None; + } + acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { + match ctx.config.snippet_cap { + Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), + None => builder.insert(ident.text_range().end(), "::<_>"), + } + }) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + use test_utils::mark; + + #[test] + fn add_turbo_fish_function() { + check_assist( + add_turbo_fish, + r#" +fn make() -> T {} +fn main() { + make<|>(); +} +"#, + r#" +fn make() -> T {} +fn main() { + make::<${0:_}>(); +} +"#, + ); + } + + #[test] + fn add_turbo_fish_method() { + check_assist( + add_turbo_fish, + r#" +struct S; +impl S { + fn make(&self) -> T {} +} +fn main() { + S.make<|>(); +} +"#, + r#" +struct S; +impl S { + fn make(&self) -> T {} +} +fn main() { + S.make::<${0:_}>(); +} +"#, + ); + } + + #[test] + fn add_turbo_fish_one_fish_is_enough() { + mark::check!(add_turbo_fish_one_fish_is_enough); + check_assist_not_applicable( + add_turbo_fish, + r#" +fn make() -> T {} +fn main() { + make<|>::<()>(); +} +"#, + ); + } + + #[test] + fn add_turbo_fish_non_generic() { + mark::check!(add_turbo_fish_non_generic); + check_assist_not_applicable( + add_turbo_fish, + r#" +fn make() -> () {} +fn main() { + make<|>(); +} +"#, + ); + } +} diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs index 260b9e073205..233e8fb8e65c 100644 --- a/crates/ra_assists/src/handlers/apply_demorgan.rs +++ b/crates/ra_assists/src/handlers/apply_demorgan.rs @@ -1,6 +1,6 @@ use ra_syntax::ast::{self, AstNode}; -use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; +use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; // Assist: apply_demorgan // @@ -21,7 +21,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; // if !(x == 4 && y) {} // } // ``` -pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option { +pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let expr = ctx.find_node_at_offset::()?; let op = expr.op_kind()?; let op_range = expr.op_token()?.text_range(); @@ -39,8 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option { let rhs_range = rhs.syntax().text_range(); let not_rhs = invert_boolean_expression(rhs); - ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| { - edit.target(op_range); + acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { edit.replace(op_range, opposite_op); edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); @@ -60,26 +59,26 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; #[test] fn demorgan_turns_and_into_or() { - check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x ||<|> x) }") + check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }") } #[test] fn demorgan_turns_or_into_and() { - check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x &&<|> x) }") + check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }") } #[test] fn demorgan_removes_inequality() { - check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x &&<|> x) }") + check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }") } #[test] fn demorgan_general_case() { - check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }") + check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }") } #[test] diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 99682e023e92..edf96d50ec14 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -1,5 +1,6 @@ use std::collections::BTreeSet; +use either::Either; use hir::{ AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, Type, @@ -12,12 +13,7 @@ use ra_syntax::{ }; use rustc_hash::FxHashSet; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - utils::insert_use_statement, - AssistId, -}; -use either::Either; +use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; // Assist: auto_import // @@ -38,25 +34,32 @@ use either::Either; // } // # pub mod std { pub mod collections { pub struct HashMap { } } } // ``` -pub(crate) fn auto_import(ctx: AssistCtx) -> Option { +pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let auto_import_assets = AutoImportAssets::new(&ctx)?; let proposed_imports = auto_import_assets.search_for_imports(ctx.db); if proposed_imports.is_empty() { return None; } - let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); + let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; + let group = auto_import_assets.get_import_group_message(); for import in proposed_imports { - group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { - edit.target(auto_import_assets.syntax_under_caret.text_range()); - insert_use_statement( - &auto_import_assets.syntax_under_caret, - &import, - edit.text_edit_builder(), - ); - }); + acc.add_group( + &group, + AssistId("auto_import"), + format!("Import `{}`", &import), + range, + |builder| { + insert_use_statement( + &auto_import_assets.syntax_under_caret, + &import, + ctx, + builder.text_edit_builder(), + ); + }, + ); } - group.finish() + Some(()) } #[derive(Debug)] @@ -67,15 +70,15 @@ struct AutoImportAssets { } impl AutoImportAssets { - fn new(ctx: &AssistCtx) -> Option { - if let Some(path_under_caret) = ctx.find_node_at_offset::() { + fn new(ctx: &AssistContext) -> Option { + if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::() { Self::for_regular_path(path_under_caret, &ctx) } else { - Self::for_method_call(ctx.find_node_at_offset()?, &ctx) + Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx) } } - fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option { + fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option { let syntax_under_caret = method_call.syntax().to_owned(); let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; Some(Self { @@ -85,7 +88,7 @@ impl AutoImportAssets { }) } - fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option { + fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option { let syntax_under_caret = path_under_caret.syntax().to_owned(); if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { return None; @@ -108,8 +111,8 @@ impl AutoImportAssets { } } - fn get_import_group_message(&self) -> String { - match &self.import_candidate { + fn get_import_group_message(&self) -> GroupLabel { + let name = match &self.import_candidate { ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), ImportCandidate::QualifierStart(qualifier_start) => { format!("Import {}", qualifier_start) @@ -120,7 +123,8 @@ impl AutoImportAssets { ImportCandidate::TraitMethod(_, trait_method_name) => { format!("Import a trait for method {}", trait_method_name) } - } + }; + GroupLabel(name) } fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet { @@ -280,7 +284,7 @@ impl ImportCandidate { #[cfg(test)] mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; #[test] fn applicable_when_found_an_import() { @@ -294,7 +298,7 @@ mod tests { } ", r" - <|>use PubMod::PubStruct; + use PubMod::PubStruct; PubStruct @@ -305,6 +309,35 @@ mod tests { ); } + #[test] + fn applicable_when_found_an_import_in_macros() { + check_assist( + auto_import, + r" + macro_rules! foo { + ($i:ident) => { fn foo(a: $i) {} } + } + foo!(Pub<|>Struct); + + pub mod PubMod { + pub struct PubStruct; + } + ", + r" + use PubMod::PubStruct; + + macro_rules! foo { + ($i:ident) => { fn foo(a: $i) {} } + } + foo!(PubStruct); + + pub mod PubMod { + pub struct PubStruct; + } + ", + ); + } + #[test] fn auto_imports_are_merged() { check_assist( @@ -327,7 +360,7 @@ mod tests { use PubMod::{PubStruct2, PubStruct1}; struct Test { - test: Pub<|>Struct2, + test: PubStruct2, } pub mod PubMod { @@ -358,9 +391,9 @@ mod tests { } ", r" - use PubMod1::PubStruct; + use PubMod3::PubStruct; - PubSt<|>ruct + PubStruct pub mod PubMod1 { pub struct PubStruct; @@ -441,7 +474,7 @@ mod tests { r" use PubMod::test_function; - test_function<|> + test_function pub mod PubMod { pub fn test_function() {}; @@ -468,7 +501,7 @@ mod tests { r"use crate_with_macro::foo; fn main() { - foo<|> + foo } ", ); @@ -554,7 +587,7 @@ fn main() { } fn main() { - TestStruct::test_function<|> + TestStruct::test_function } ", ); @@ -587,7 +620,7 @@ fn main() { } fn main() { - TestStruct::TEST_CONST<|> + TestStruct::TEST_CONST } ", ); @@ -626,7 +659,7 @@ fn main() { } fn main() { - test_mod::TestStruct::test_function<|> + test_mod::TestStruct::test_function } ", ); @@ -697,7 +730,7 @@ fn main() { } fn main() { - test_mod::TestStruct::TEST_CONST<|> + test_mod::TestStruct::TEST_CONST } ", ); @@ -770,7 +803,7 @@ fn main() { fn main() { let test_struct = test_mod::TestStruct {}; - test_struct.test_meth<|>od() + test_struct.test_method() } ", ); diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs new file mode 100644 index 000000000000..c6baa0a57c6c --- /dev/null +++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs @@ -0,0 +1,961 @@ +use ra_syntax::{ + ast::{self, BlockExpr, Expr, LoopBodyOwner}, + AstNode, SyntaxNode, +}; + +use crate::{AssistContext, AssistId, Assists}; + +// Assist: change_return_type_to_result +// +// Change the function's return type to Result. +// +// ``` +// fn foo() -> i32<|> { 42i32 } +// ``` +// -> +// ``` +// fn foo() -> Result { Ok(42i32) } +// ``` +pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let ret_type = ctx.find_node_at_offset::()?; + // FIXME: extend to lambdas as well + let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; + + let type_ref = &ret_type.type_ref()?; + if type_ref.syntax().text().to_string().starts_with("Result<") { + return None; + } + + let block_expr = &fn_def.body()?; + + acc.add( + AssistId("change_return_type_to_result"), + "Change return type to Result", + type_ref.syntax().text_range(), + |builder| { + let mut tail_return_expr_collector = TailReturnCollector::new(); + tail_return_expr_collector.collect_jump_exprs(block_expr, false); + tail_return_expr_collector.collect_tail_exprs(block_expr); + + for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { + builder.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg)); + } + + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = format!("Result<{}, ${{0:_}}>", type_ref); + builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet) + } + None => builder + .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)), + } + }, + ) +} + +struct TailReturnCollector { + exprs_to_wrap: Vec, +} + +impl TailReturnCollector { + fn new() -> Self { + Self { exprs_to_wrap: vec![] } + } + /// Collect all`return` expression + fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) { + let statements = block_expr.statements(); + for stmt in statements { + let expr = match &stmt { + ast::Stmt::ExprStmt(stmt) => stmt.expr(), + ast::Stmt::LetStmt(stmt) => stmt.initializer(), + }; + if let Some(expr) = &expr { + self.handle_exprs(expr, collect_break); + } + } + + // Browse tail expressions for each block + if let Some(expr) = block_expr.expr() { + if let Some(last_exprs) = get_tail_expr_from_block(&expr) { + for last_expr in last_exprs { + let last_expr = match last_expr { + NodeType::Node(expr) | NodeType::Leaf(expr) => expr, + }; + + if let Some(last_expr) = Expr::cast(last_expr.clone()) { + self.handle_exprs(&last_expr, collect_break); + } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) { + let expr_stmt = match &expr_stmt { + ast::Stmt::ExprStmt(stmt) => stmt.expr(), + ast::Stmt::LetStmt(stmt) => stmt.initializer(), + }; + if let Some(expr) = &expr_stmt { + self.handle_exprs(expr, collect_break); + } + } + } + } + } + } + + fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) { + match expr { + Expr::BlockExpr(block_expr) => { + self.collect_jump_exprs(&block_expr, collect_break); + } + Expr::ReturnExpr(ret_expr) => { + if let Some(ret_expr_arg) = &ret_expr.expr() { + self.exprs_to_wrap.push(ret_expr_arg.syntax().clone()); + } + } + Expr::BreakExpr(break_expr) if collect_break => { + if let Some(break_expr_arg) = &break_expr.expr() { + self.exprs_to_wrap.push(break_expr_arg.syntax().clone()); + } + } + Expr::IfExpr(if_expr) => { + for block in if_expr.blocks() { + self.collect_jump_exprs(&block, collect_break); + } + } + Expr::LoopExpr(loop_expr) => { + if let Some(block_expr) = loop_expr.loop_body() { + self.collect_jump_exprs(&block_expr, collect_break); + } + } + Expr::ForExpr(for_expr) => { + if let Some(block_expr) = for_expr.loop_body() { + self.collect_jump_exprs(&block_expr, collect_break); + } + } + Expr::WhileExpr(while_expr) => { + if let Some(block_expr) = while_expr.loop_body() { + self.collect_jump_exprs(&block_expr, collect_break); + } + } + Expr::MatchExpr(match_expr) => { + if let Some(arm_list) = match_expr.match_arm_list() { + arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| { + self.handle_exprs(&expr, collect_break); + }); + } + } + _ => {} + } + } + + fn collect_tail_exprs(&mut self, block: &BlockExpr) { + if let Some(expr) = block.expr() { + self.handle_exprs(&expr, true); + self.fetch_tail_exprs(&expr); + } + } + + fn fetch_tail_exprs(&mut self, expr: &Expr) { + if let Some(exprs) = get_tail_expr_from_block(expr) { + for node_type in &exprs { + match node_type { + NodeType::Leaf(expr) => { + self.exprs_to_wrap.push(expr.clone()); + } + NodeType::Node(expr) => match &Expr::cast(expr.clone()) { + Some(last_expr) => { + self.fetch_tail_exprs(last_expr); + } + None => { + self.exprs_to_wrap.push(expr.clone()); + } + }, + } + } + } + } +} + +#[derive(Debug)] +enum NodeType { + Leaf(SyntaxNode), + Node(SyntaxNode), +} + +/// Get a tail expression inside a block +fn get_tail_expr_from_block(expr: &Expr) -> Option> { + match expr { + Expr::IfExpr(if_expr) => { + let mut nodes = vec![]; + for block in if_expr.blocks() { + if let Some(block_expr) = block.expr() { + if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) { + nodes.extend(tail_exprs); + } + } else if let Some(last_expr) = block.syntax().last_child() { + nodes.push(NodeType::Node(last_expr)); + } else { + nodes.push(NodeType::Node(block.syntax().clone())); + } + } + Some(nodes) + } + Expr::LoopExpr(loop_expr) => { + loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) + } + Expr::ForExpr(for_expr) => { + for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) + } + Expr::WhileExpr(while_expr) => { + while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) + } + Expr::BlockExpr(block_expr) => { + block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())]) + } + Expr::MatchExpr(match_expr) => { + let arm_list = match_expr.match_arm_list()?; + let arms: Vec = arm_list + .arms() + .filter_map(|match_arm| match_arm.expr()) + .map(|expr| match expr { + Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()), + Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()), + _ => match expr.syntax().last_child() { + Some(last_expr) => NodeType::Node(last_expr), + None => NodeType::Node(expr.syntax().clone()), + }, + }) + .collect(); + + Some(arms) + } + Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]), + Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]), + Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]), + Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]), + Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn change_return_type_to_result_simple() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i3<|>2 { + let test = "test"; + return 42i32; + }"#, + r#"fn foo() -> Result { + let test = "test"; + return Ok(42i32); + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_return_type() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let test = "test"; + return 42i32; + }"#, + r#"fn foo() -> Result { + let test = "test"; + return Ok(42i32); + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_return_type_bad_cursor() { + check_assist_not_applicable( + change_return_type_to_result, + r#"fn foo() -> i32 { + let test = "test";<|> + return 42i32; + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_cursor() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> <|>i32 { + let test = "test"; + return 42i32; + }"#, + r#"fn foo() -> Result { + let test = "test"; + return Ok(42i32); + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail() { + check_assist( + change_return_type_to_result, + r#"fn foo() -><|> i32 { + let test = "test"; + 42i32 + }"#, + r#"fn foo() -> Result { + let test = "test"; + Ok(42i32) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_only() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + 42i32 + }"#, + r#"fn foo() -> Result { + Ok(42i32) + }"#, + ); + } + #[test] + fn change_return_type_to_result_simple_with_tail_block_like() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + if true { + 42i32 + } else { + 24i32 + } + }"#, + r#"fn foo() -> Result { + if true { + Ok(42i32) + } else { + Ok(24i32) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_nested_if() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + if true { + if false { + 1 + } else { + 2 + } + } else { + 24i32 + } + }"#, + r#"fn foo() -> Result { + if true { + if false { + Ok(1) + } else { + Ok(2) + } + } else { + Ok(24i32) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_await() { + check_assist( + change_return_type_to_result, + r#"async fn foo() -> i<|>32 { + if true { + if false { + 1.await + } else { + 2.await + } + } else { + 24i32.await + } + }"#, + r#"async fn foo() -> Result { + if true { + if false { + Ok(1.await) + } else { + Ok(2.await) + } + } else { + Ok(24i32.await) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_array() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> [i32;<|> 3] { + [1, 2, 3] + }"#, + r#"fn foo() -> Result<[i32; 3], ${0:_}> { + Ok([1, 2, 3]) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_cast() { + check_assist( + change_return_type_to_result, + r#"fn foo() -<|>> i32 { + if true { + if false { + 1 as i32 + } else { + 2 as i32 + } + } else { + 24 as i32 + } + }"#, + r#"fn foo() -> Result { + if true { + if false { + Ok(1 as i32) + } else { + Ok(2 as i32) + } + } else { + Ok(24 as i32) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_match() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + match my_var { + 5 => 42i32, + _ => 24i32, + } + }"#, + r#"fn foo() -> Result { + let my_var = 5; + match my_var { + 5 => Ok(42i32), + _ => Ok(24i32), + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_loop_with_tail() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + loop { + println!("test"); + 5 + } + + my_var + }"#, + r#"fn foo() -> Result { + let my_var = 5; + loop { + println!("test"); + 5 + } + + Ok(my_var) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_loop_in_let_stmt() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = let x = loop { + break 1; + }; + + my_var + }"#, + r#"fn foo() -> Result { + let my_var = let x = loop { + break 1; + }; + + Ok(my_var) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + let res = match my_var { + 5 => 42i32, + _ => return 24i32, + }; + + res + }"#, + r#"fn foo() -> Result { + let my_var = 5; + let res = match my_var { + 5 => 42i32, + _ => return Ok(24i32), + }; + + Ok(res) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + let res = if my_var == 5 { + 42i32 + } else { + return 24i32; + }; + + res + }"#, + r#"fn foo() -> Result { + let my_var = 5; + let res = if my_var == 5 { + 42i32 + } else { + return Ok(24i32); + }; + + Ok(res) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + match my_var { + 5 => { + if true { + 42i32 + } else { + 25i32 + } + }, + _ => { + let test = "test"; + if test == "test" { + return bar(); + } + 53i32 + }, + } + }"#, + r#"fn foo() -> Result { + let my_var = 5; + match my_var { + 5 => { + if true { + Ok(42i32) + } else { + Ok(25i32) + } + }, + _ => { + let test = "test"; + if test == "test" { + return Ok(bar()); + } + Ok(53i32) + }, + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_early_return() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i<|>32 { + let test = "test"; + if test == "test" { + return 24i32; + } + 53i32 + }"#, + r#"fn foo() -> Result { + let test = "test"; + if test == "test" { + return Ok(24i32); + } + Ok(53i32) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_closure() { + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -><|> u32 { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return 99; + } else { + return 0; + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return Ok(99); + } else { + return Ok(0); + } + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u32<|> { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return 99; + } else { + return 0; + } + } + let t = None; + + t.unwrap_or_else(|| the_field) + }"#, + r#"fn foo(the_field: u32) -> Result { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return Ok(99); + } else { + return Ok(0); + } + } + let t = None; + + Ok(t.unwrap_or_else(|| the_field)) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_weird_forms() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let test = "test"; + if test == "test" { + return 24i32; + } + let mut i = 0; + loop { + if i == 1 { + break 55; + } + i += 1; + } + }"#, + r#"fn foo() -> Result { + let test = "test"; + if test == "test" { + return Ok(24i32); + } + let mut i = 0; + loop { + if i == 1 { + break Ok(55); + } + i += 1; + } + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let test = "test"; + if test == "test" { + return 24i32; + } + let mut i = 0; + loop { + loop { + if i == 1 { + break 55; + } + i += 1; + } + } + }"#, + r#"fn foo() -> Result { + let test = "test"; + if test == "test" { + return Ok(24i32); + } + let mut i = 0; + loop { + loop { + if i == 1 { + break Ok(55); + } + i += 1; + } + } + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo() -> i3<|>2 { + let test = "test"; + let other = 5; + if test == "test" { + let res = match other { + 5 => 43, + _ => return 56, + }; + } + let mut i = 0; + loop { + loop { + if i == 1 { + break 55; + } + i += 1; + } + } + }"#, + r#"fn foo() -> Result { + let test = "test"; + let other = 5; + if test == "test" { + let res = match other { + 5 => 43, + _ => return Ok(56), + }; + } + let mut i = 0; + loop { + loop { + if i == 1 { + break Ok(55); + } + i += 1; + } + } + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u32<|> { + if the_field < 5 { + let mut i = 0; + loop { + if i > 5 { + return 55u32; + } + i += 3; + } + + match i { + 5 => return 99, + _ => return 0, + }; + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result { + if the_field < 5 { + let mut i = 0; + loop { + if i > 5 { + return Ok(55u32); + } + i += 3; + } + + match i { + 5 => return Ok(99), + _ => return Ok(0), + }; + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u3<|>2 { + if the_field < 5 { + let mut i = 0; + + match i { + 5 => return 99, + _ => return 0, + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result { + if the_field < 5 { + let mut i = 0; + + match i { + 5 => return Ok(99), + _ => return Ok(0), + } + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u32<|> { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return 99 + } else { + return 0 + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return Ok(99) + } else { + return Ok(0) + } + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> <|>u32 { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return 99; + } else { + return 0; + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return Ok(99); + } else { + return Ok(0); + } + } + + Ok(the_field) + }"#, + ); + } +} diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index 44f6a1dae02c..c21d75be080c 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs @@ -7,9 +7,9 @@ use ra_syntax::{ }, SyntaxNode, TextSize, T, }; +use test_utils::mark; -use crate::{Assist, AssistCtx, AssistId}; -use test_utils::tested_by; +use crate::{AssistContext, AssistId, Assists}; // Assist: change_visibility // @@ -22,14 +22,14 @@ use test_utils::tested_by; // ``` // pub(crate) fn frobnicate() {} // ``` -pub(crate) fn change_visibility(ctx: AssistCtx) -> Option { +pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { if let Some(vis) = ctx.find_node_at_offset::() { - return change_vis(ctx, vis); + return change_vis(acc, vis); } - add_vis(ctx) + add_vis(acc, ctx) } -fn add_vis(ctx: AssistCtx) -> Option { +fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, _ => false, @@ -47,23 +47,27 @@ fn add_vis(ctx: AssistCtx) -> Option { return None; } (vis_offset(&parent), keyword.text_range()) - } else { - let field_name: ast::Name = ctx.find_node_at_offset()?; + } else if let Some(field_name) = ctx.find_node_at_offset::() { let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; if field.name()? != field_name { - tested_by!(change_visibility_field_false_positive); + mark::hit!(change_visibility_field_false_positive); return None; } if field.visibility().is_some() { return None; } (vis_offset(field.syntax()), field_name.syntax().text_range()) + } else if let Some(field) = ctx.find_node_at_offset::() { + if field.visibility().is_some() { + return None; + } + (vis_offset(field.syntax()), field.syntax().text_range()) + } else { + return None; }; - ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| { - edit.target(target); + acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { edit.insert(offset, "pub(crate) "); - edit.set_cursor(offset); }) } @@ -78,49 +82,49 @@ fn vis_offset(node: &SyntaxNode) -> TextSize { .unwrap_or_else(|| node.text_range().start()) } -fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option { +fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { if vis.syntax().text() == "pub" { - return ctx.add_assist( + let target = vis.syntax().text_range(); + return acc.add( AssistId("change_visibility"), "Change Visibility to pub(crate)", + target, |edit| { - edit.target(vis.syntax().text_range()); edit.replace(vis.syntax().text_range(), "pub(crate)"); - edit.set_cursor(vis.syntax().text_range().start()) }, ); } if vis.syntax().text() == "pub(crate)" { - return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { - edit.target(vis.syntax().text_range()); - edit.replace(vis.syntax().text_range(), "pub"); - edit.set_cursor(vis.syntax().text_range().start()); - }); + let target = vis.syntax().text_range(); + return acc.add( + AssistId("change_visibility"), + "Change visibility to pub", + target, + |edit| { + edit.replace(vis.syntax().text_range(), "pub"); + }, + ); } None } #[cfg(test)] mod tests { - use test_utils::covers; + use test_utils::mark; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use super::*; #[test] fn change_visibility_adds_pub_crate_to_items() { - check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); - check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); - check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); - check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); - check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); - check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); - check_assist( - change_visibility, - "unsafe f<|>n foo() {}", - "<|>pub(crate) unsafe fn foo() {}", - ); + check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}"); + check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}"); + check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}"); + check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}"); + check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}"); + check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}"); + check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}"); } #[test] @@ -128,13 +132,14 @@ mod tests { check_assist( change_visibility, r"struct S { <|>field: u32 }", - r"struct S { <|>pub(crate) field: u32 }", - ) + r"struct S { pub(crate) field: u32 }", + ); + check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )"); } #[test] fn change_visibility_field_false_positive() { - covers!(change_visibility_field_false_positive); + mark::check!(change_visibility_field_false_positive); check_assist_not_applicable( change_visibility, r"struct S { field: [(); { let <|>x = ();}] }", @@ -143,17 +148,17 @@ mod tests { #[test] fn change_visibility_pub_to_pub_crate() { - check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") + check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}") } #[test] fn change_visibility_pub_crate_to_pub() { - check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") + check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}") } #[test] fn change_visibility_const() { - check_assist(change_visibility, "<|>const FOO = 3u8;", "<|>pub(crate) const FOO = 3u8;"); + check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;"); } #[test] @@ -174,11 +179,20 @@ mod tests { // comments #[derive(Debug)] - <|>pub(crate) struct Foo; + pub(crate) struct Foo; ", ) } + #[test] + fn not_applicable_for_enum_variants() { + check_assist_not_applicable( + change_visibility, + r"mod foo { pub enum Foo {Foo1} } + fn main() { foo::Foo::Foo1<|> } ", + ); + } + #[test] fn change_visibility_target() { check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index ea6c56f8cffe..4cc75a7ce2dc 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs @@ -2,14 +2,18 @@ use std::{iter::once, ops::RangeInclusive}; use ra_syntax::{ algo::replace_children, - ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat}, + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, + }, AstNode, SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, SyntaxNode, }; use crate::{ - assist_ctx::{Assist, AssistCtx}, + assist_context::{AssistContext, Assists}, utils::invert_boolean_expression, AssistId, }; @@ -36,7 +40,7 @@ use crate::{ // bar(); // } // ``` -pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { +pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; if if_expr.else_branch().is_some() { return None; @@ -47,7 +51,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { // Check if there is an IfLet that we can handle. let if_let_pat = match cond.pat() { None => None, // No IfLet, supported. - Some(TupleStructPat(pat)) if pat.args().count() == 1 => { + Some(ast::Pat::TupleStructPat(pat)) if pat.args().count() == 1 => { let path = pat.path()?; match path.qualifier() { None => { @@ -61,9 +65,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { }; let cond_expr = cond.expr()?; - let then_block = if_expr.then_branch()?.block()?; + let then_block = if_expr.then_branch()?; - let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?; + let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; if parent_block.expr()? != if_expr.clone().into() { return None; @@ -80,7 +84,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { return None; } - let parent_container = parent_block.syntax().parent()?.parent()?; + let parent_container = parent_block.syntax().parent()?; let early_expression: ast::Expr = match parent_container.kind() { WHILE_EXPR | LOOP_EXPR => make::expr_continue(), @@ -93,9 +97,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { } then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; - let cursor_position = ctx.frange.range.start(); - ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| { + let target = if_expr.syntax().text_range(); + acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); let new_block = match if_let_pat { None => { @@ -104,8 +108,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { let then_branch = make::block_expr(once(make::expr_stmt(early_expression).into()), None); let cond = invert_boolean_expression(cond_expr); - let e = make::expr_if(make::condition(cond, None), then_branch); - if_indent_level.increase_indent(e) + make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) }; replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) } @@ -139,21 +142,19 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), Some(match_expr), ); - let let_stmt = if_indent_level.increase_indent(let_stmt); + let let_stmt = let_stmt.indent(if_indent_level); replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) } }; - edit.target(if_expr.syntax().text_range()); - edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); - edit.set_cursor(cursor_position); + edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); fn replace( new_expr: &SyntaxNode, - then_block: &Block, - parent_block: &Block, + then_block: &ast::BlockExpr, + parent_block: &ast::BlockExpr, if_expr: &ast::IfExpr, ) -> SyntaxNode { - let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); + let then_block_items = then_block.dedent(IndentLevel::from(1)); let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { @@ -182,7 +183,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -204,7 +205,7 @@ mod tests { r#" fn main() { bar(); - if<|> !true { + if !true { return; } foo(); @@ -234,7 +235,7 @@ mod tests { r#" fn main(n: Option) { bar(); - le<|>t n = match n { + let n = match n { Some(it) => it, _ => return, }; @@ -260,7 +261,7 @@ mod tests { "#, r#" fn main() { - le<|>t x = match Err(92) { + let x = match Err(92) { Ok(it) => it, _ => return, }; @@ -288,7 +289,7 @@ mod tests { r#" fn main(n: Option) { bar(); - le<|>t n = match n { + let n = match n { Ok(it) => it, _ => return, }; @@ -318,7 +319,7 @@ mod tests { r#" fn main() { while true { - if<|> !true { + if !true { continue; } foo(); @@ -346,7 +347,7 @@ mod tests { r#" fn main() { while true { - le<|>t n = match n { + let n = match n { Some(it) => it, _ => continue, }; @@ -375,7 +376,7 @@ mod tests { r#" fn main() { loop { - if<|> !true { + if !true { continue; } foo(); @@ -403,7 +404,7 @@ mod tests { r#" fn main() { loop { - le<|>t n = match n { + let n = match n { Some(it) => it, _ => continue, }; diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index 8d1af99336f0..cc303285b35d 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs @@ -4,8 +4,12 @@ use hir::{Adt, HasSource, ModuleDef, Semantics}; use itertools::Itertools; use ra_ide_db::RootDatabase; use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; +use test_utils::mark; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{ + utils::{render_snippet, Cursor, FamousDefs}, + AssistContext, AssistId, Assists, +}; // Assist: fill_match_arms // @@ -26,12 +30,12 @@ use crate::{Assist, AssistCtx, AssistId}; // // fn handle(action: Action) { // match action { -// Action::Move { distance } => {} +// $0Action::Move { distance } => {} // Action::Stop => {} // } // } // ``` -pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { +pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let match_expr = ctx.find_node_at_offset::()?; let match_arm_list = match_expr.match_arm_list()?; @@ -49,12 +53,18 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { let missing_arms: Vec = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { let variants = enum_def.variants(ctx.db); - variants + let mut variants = variants .into_iter() .filter_map(|variant| build_pat(ctx.db, module, variant)) .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) - .collect() + .collect::>(); + if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() { + // Match `Some` variant first. + mark::hit!(option_order); + variants.reverse() + } + variants } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { // Partial fill not currently supported for tuple of enums. if !arms.is_empty() { @@ -92,12 +102,24 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { - let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); - - edit.target(match_expr.syntax().text_range()); - edit.set_cursor(expr.syntax().text_range().start()); - edit.replace_ast(match_arm_list, new_arm_list); + let target = match_expr.syntax().text_range(); + acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| { + let new_arm_list = match_arm_list.remove_placeholder(); + let n_old_arms = new_arm_list.arms().count(); + let new_arm_list = new_arm_list.append_arms(missing_arms); + let first_new_arm = new_arm_list.arms().nth(n_old_arms); + let old_range = match_arm_list.syntax().text_range(); + match (first_new_arm, ctx.config.snippet_cap) { + (Some(first_new_arm), Some(cap)) => { + let snippet = render_snippet( + cap, + new_arm_list.syntax(), + Cursor::Before(first_new_arm.syntax()), + ); + builder.replace_snippet(cap, old_range, snippet); + } + _ => builder.replace(old_range, new_arm_list.to_string()), + } }) } @@ -168,7 +190,12 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use test_utils::mark; + + use crate::{ + tests::{check_assist, check_assist_not_applicable, check_assist_target}, + utils::FamousDefs, + }; use super::fill_match_arms; @@ -215,12 +242,12 @@ mod tests { r#" enum A { As, - Bs{x:i32, y:Option}, + Bs { x: i32, y: Option }, Cs(i32, Option), } fn main() { match A::As<|> { - A::Bs{x,y:Some(_)} => {} + A::Bs { x, y: Some(_) } => {} A::Cs(_, Some(_)) => {} } } @@ -228,14 +255,14 @@ mod tests { r#" enum A { As, - Bs{x:i32, y:Option}, + Bs { x: i32, y: Option }, Cs(i32, Option), } fn main() { - match <|>A::As { - A::Bs{x,y:Some(_)} => {} + match A::As { + A::Bs { x, y: Some(_) } => {} A::Cs(_, Some(_)) => {} - A::As => {} + $0A::As => {} } } "#, @@ -265,9 +292,9 @@ mod tests { Cs(Option), } fn main() { - match <|>A::As { + match A::As { A::Cs(_) | A::Bs => {} - A::As => {} + $0A::As => {} } } "#, @@ -311,11 +338,11 @@ mod tests { Ys, } fn main() { - match <|>A::As { + match A::As { A::Bs if 0 < 1 => {} A::Ds(_value) => { let x = 1; } A::Es(B::Xs) => (), - A::As => {} + $0A::As => {} A::Cs => {} } } @@ -333,7 +360,7 @@ mod tests { Bs, Cs(String), Ds(String, String), - Es{ x: usize, y: usize } + Es { x: usize, y: usize } } fn main() { @@ -347,13 +374,13 @@ mod tests { Bs, Cs(String), Ds(String, String), - Es{ x: usize, y: usize } + Es { x: usize, y: usize } } fn main() { let a = A::As; - match <|>a { - A::As => {} + match a { + $0A::As => {} A::Bs => {} A::Cs(_) => {} A::Ds(_, _) => {} @@ -369,14 +396,8 @@ mod tests { check_assist( fill_match_arms, r#" - enum A { - One, - Two, - } - enum B { - One, - Two, - } + enum A { One, Two } + enum B { One, Two } fn main() { let a = A::One; @@ -385,20 +406,14 @@ mod tests { } "#, r#" - enum A { - One, - Two, - } - enum B { - One, - Two, - } + enum A { One, Two } + enum B { One, Two } fn main() { let a = A::One; let b = B::One; - match <|>(a, b) { - (A::One, B::One) => {} + match (a, b) { + $0(A::One, B::One) => {} (A::One, B::Two) => {} (A::Two, B::One) => {} (A::Two, B::Two) => {} @@ -413,14 +428,8 @@ mod tests { check_assist( fill_match_arms, r#" - enum A { - One, - Two, - } - enum B { - One, - Two, - } + enum A { One, Two } + enum B { One, Two } fn main() { let a = A::One; @@ -429,20 +438,14 @@ mod tests { } "#, r#" - enum A { - One, - Two, - } - enum B { - One, - Two, - } + enum A { One, Two } + enum B { One, Two } fn main() { let a = A::One; let b = B::One; - match <|>(&a, &b) { - (A::One, B::One) => {} + match (&a, &b) { + $0(A::One, B::One) => {} (A::One, B::Two) => {} (A::Two, B::One) => {} (A::Two, B::Two) => {} @@ -457,14 +460,8 @@ mod tests { check_assist_not_applicable( fill_match_arms, r#" - enum A { - One, - Two, - } - enum B { - One, - Two, - } + enum A { One, Two } + enum B { One, Two } fn main() { let a = A::One; @@ -482,14 +479,8 @@ mod tests { check_assist_not_applicable( fill_match_arms, r#" - enum A { - One, - Two, - } - enum B { - One, - Two, - } + enum A { One, Two } + enum B { One, Two } fn main() { let a = A::One; @@ -513,10 +504,7 @@ mod tests { check_assist_not_applicable( fill_match_arms, r#" - enum A { - One, - Two, - } + enum A { One, Two } fn main() { let a = A::One; @@ -532,9 +520,7 @@ mod tests { check_assist( fill_match_arms, r#" - enum A { - As, - } + enum A { As } fn foo(a: &A) { match a<|> { @@ -542,13 +528,11 @@ mod tests { } "#, r#" - enum A { - As, - } + enum A { As } fn foo(a: &A) { - match <|>a { - A::As => {} + match a { + $0A::As => {} } } "#, @@ -558,7 +542,7 @@ mod tests { fill_match_arms, r#" enum A { - Es{ x: usize, y: usize } + Es { x: usize, y: usize } } fn foo(a: &mut A) { @@ -568,12 +552,12 @@ mod tests { "#, r#" enum A { - Es{ x: usize, y: usize } + Es { x: usize, y: usize } } fn foo(a: &mut A) { - match <|>a { - A::Es { x, y } => {} + match a { + $0A::Es { x, y } => {} } } "#, @@ -612,8 +596,8 @@ mod tests { enum E { X, Y } fn main() { - match <|>E::X { - E::X => {} + match E::X { + $0E::X => {} E::Y => {} } } @@ -640,8 +624,8 @@ mod tests { use foo::E::X; fn main() { - match <|>X { - X => {} + match X { + $0X => {} foo::E::Y => {} } } @@ -654,10 +638,7 @@ mod tests { check_assist( fill_match_arms, r#" - enum A { - One, - Two, - } + enum A { One, Two } fn foo(a: A) { match a { // foo bar baz<|> @@ -667,16 +648,13 @@ mod tests { } "#, r#" - enum A { - One, - Two, - } + enum A { One, Two } fn foo(a: A) { - match <|>a { + match a { // foo bar baz A::One => {} // This is where the rest should be - A::Two => {} + $0A::Two => {} } } "#, @@ -688,10 +666,7 @@ mod tests { check_assist( fill_match_arms, r#" - enum A { - One, - Two, - } + enum A { One, Two } fn foo(a: A) { match a { // foo bar baz<|> @@ -699,14 +674,11 @@ mod tests { } "#, r#" - enum A { - One, - Two, - } + enum A { One, Two } fn foo(a: A) { - match <|>a { + match a { // foo bar baz - A::One => {} + $0A::One => {} A::Two => {} } } @@ -729,12 +701,37 @@ mod tests { r#" enum A { One, Two, } fn foo(a: A) { - match <|>a { - A::One => {} + match a { + $0A::One => {} A::Two => {} } } "#, ); } + + #[test] + fn option_order() { + mark::check!(option_order); + let before = r#" +fn foo(opt: Option) { + match opt<|> { + } +}"#; + let before = + &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); + + check_assist( + fill_match_arms, + before, + r#" +fn foo(opt: Option) { + match opt { + $0Some(_) => {} + None => {} + } +} +"#, + ); + } } diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs new file mode 100644 index 000000000000..9ec42f568c59 --- /dev/null +++ b/crates/ra_assists/src/handlers/fix_visibility.rs @@ -0,0 +1,559 @@ +use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; +use ra_db::FileId; +use ra_syntax::{ + ast, AstNode, + SyntaxKind::{ATTR, COMMENT, WHITESPACE}, + SyntaxNode, TextRange, TextSize, +}; + +use crate::{AssistContext, AssistId, Assists}; + +// FIXME: this really should be a fix for diagnostic, rather than an assist. + +// Assist: fix_visibility +// +// Makes inaccessible item public. +// +// ``` +// mod m { +// fn frobnicate() {} +// } +// fn main() { +// m::frobnicate<|>() {} +// } +// ``` +// -> +// ``` +// mod m { +// $0pub(crate) fn frobnicate() {} +// } +// fn main() { +// m::frobnicate() {} +// } +// ``` +pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + add_vis_to_referenced_module_def(acc, ctx) + .or_else(|| add_vis_to_referenced_record_field(acc, ctx)) +} + +fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let path: ast::Path = ctx.find_node_at_offset()?; + let path_res = ctx.sema.resolve_path(&path)?; + let def = match path_res { + PathResolution::Def(def) => def, + _ => return None, + }; + + let current_module = ctx.sema.scope(&path.syntax()).module()?; + let target_module = def.module(ctx.db)?; + + let vis = target_module.visibility_of(ctx.db, &def)?; + if vis.is_visible_from(ctx.db, current_module.into()) { + return None; + }; + + let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; + + let missing_visibility = + if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; + + let assist_label = match target_name { + None => format!("Change visibility to {}", missing_visibility), + Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), + }; + + acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { + builder.set_file(target_file); + match ctx.config.snippet_cap { + Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), + None => builder.insert(offset, format!("{} ", missing_visibility)), + } + }) +} + +fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let record_field: ast::RecordField = ctx.find_node_at_offset()?; + let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; + + let current_module = ctx.sema.scope(record_field.syntax()).module()?; + let visibility = record_field_def.visibility(ctx.db); + if visibility.is_visible_from(ctx.db, current_module.into()) { + return None; + } + + let parent = record_field_def.parent_def(ctx.db); + let parent_name = parent.name(ctx.db); + let target_module = parent.module(ctx.db); + + let in_file_source = record_field_def.source(ctx.db); + let (offset, target) = match in_file_source.value { + hir::FieldSource::Named(it) => { + let s = it.syntax(); + (vis_offset(s), s.text_range()) + } + hir::FieldSource::Pos(it) => { + let s = it.syntax(); + (vis_offset(s), s.text_range()) + } + }; + + let missing_visibility = + if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; + let target_file = in_file_source.file_id.original_file(ctx.db); + + let target_name = record_field_def.name(ctx.db); + let assist_label = + format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); + + acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { + builder.set_file(target_file); + match ctx.config.snippet_cap { + Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), + None => builder.insert(offset, format!("{} ", missing_visibility)), + } + }) +} + +fn target_data_for_def( + db: &dyn HirDatabase, + def: hir::ModuleDef, +) -> Option<(TextSize, TextRange, FileId, Option)> { + fn offset_target_and_file_id( + db: &dyn HirDatabase, + x: S, + ) -> (TextSize, TextRange, FileId) + where + S: HasSource, + Ast: AstNode, + { + let source = x.source(db); + let in_file_syntax = source.syntax(); + let file_id = in_file_syntax.file_id; + let syntax = in_file_syntax.value; + (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) + } + + let target_name; + let (offset, target, target_file) = match def { + hir::ModuleDef::Function(f) => { + target_name = Some(f.name(db)); + offset_target_and_file_id(db, f) + } + hir::ModuleDef::Adt(adt) => { + target_name = Some(adt.name(db)); + match adt { + hir::Adt::Struct(s) => offset_target_and_file_id(db, s), + hir::Adt::Union(u) => offset_target_and_file_id(db, u), + hir::Adt::Enum(e) => offset_target_and_file_id(db, e), + } + } + hir::ModuleDef::Const(c) => { + target_name = c.name(db); + offset_target_and_file_id(db, c) + } + hir::ModuleDef::Static(s) => { + target_name = s.name(db); + offset_target_and_file_id(db, s) + } + hir::ModuleDef::Trait(t) => { + target_name = Some(t.name(db)); + offset_target_and_file_id(db, t) + } + hir::ModuleDef::TypeAlias(t) => { + target_name = Some(t.name(db)); + offset_target_and_file_id(db, t) + } + hir::ModuleDef::Module(m) => { + target_name = m.name(db); + let in_file_source = m.declaration_source(db)?; + let file_id = in_file_source.file_id.original_file(db.upcast()); + let syntax = in_file_source.value.syntax(); + (vis_offset(syntax), syntax.text_range(), file_id) + } + // Enum variants can't be private, we can't modify builtin types + hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, + }; + + Some((offset, target, target_file, target_name)) +} + +fn vis_offset(node: &SyntaxNode) -> TextSize { + node.children_with_tokens() + .skip_while(|it| match it.kind() { + WHITESPACE | COMMENT | ATTR => true, + _ => false, + }) + .next() + .map(|it| it.text_range().start()) + .unwrap_or_else(|| node.text_range().start()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn fix_visibility_of_fn() { + check_assist( + fix_visibility, + r"mod foo { fn foo() {} } + fn main() { foo::foo<|>() } ", + r"mod foo { $0pub(crate) fn foo() {} } + fn main() { foo::foo() } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub fn foo() {} } + fn main() { foo::foo<|>() } ", + ) + } + + #[test] + fn fix_visibility_of_adt_in_submodule() { + check_assist( + fix_visibility, + r"mod foo { struct Foo; } + fn main() { foo::Foo<|> } ", + r"mod foo { $0pub(crate) struct Foo; } + fn main() { foo::Foo } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub struct Foo; } + fn main() { foo::Foo<|> } ", + ); + check_assist( + fix_visibility, + r"mod foo { enum Foo; } + fn main() { foo::Foo<|> } ", + r"mod foo { $0pub(crate) enum Foo; } + fn main() { foo::Foo } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub enum Foo; } + fn main() { foo::Foo<|> } ", + ); + check_assist( + fix_visibility, + r"mod foo { union Foo; } + fn main() { foo::Foo<|> } ", + r"mod foo { $0pub(crate) union Foo; } + fn main() { foo::Foo } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub union Foo; } + fn main() { foo::Foo<|> } ", + ); + } + + #[test] + fn fix_visibility_of_adt_in_other_file() { + check_assist( + fix_visibility, + r" + //- /main.rs + mod foo; + fn main() { foo::Foo<|> } + + //- /foo.rs + struct Foo; + ", + r"$0pub(crate) struct Foo; + +", + ); + } + + #[test] + fn fix_visibility_of_struct_field() { + check_assist( + fix_visibility, + r"mod foo { pub struct Foo { bar: (), } } + fn main() { foo::Foo { <|>bar: () }; } ", + r"mod foo { pub struct Foo { $0pub(crate) bar: (), } } + fn main() { foo::Foo { bar: () }; } ", + ); + check_assist( + fix_visibility, + r"//- /lib.rs + mod foo; + fn main() { foo::Foo { <|>bar: () }; } + //- /foo.rs + pub struct Foo { bar: () } + ", + r"pub struct Foo { $0pub(crate) bar: () } + +", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub struct Foo { pub bar: (), } } + fn main() { foo::Foo { <|>bar: () }; } ", + ); + check_assist_not_applicable( + fix_visibility, + r"//- /lib.rs + mod foo; + fn main() { foo::Foo { <|>bar: () }; } + //- /foo.rs + pub struct Foo { pub bar: () } + ", + ); + } + + #[test] + fn fix_visibility_of_enum_variant_field() { + check_assist( + fix_visibility, + r"mod foo { pub enum Foo { Bar { bar: () } } } + fn main() { foo::Foo::Bar { <|>bar: () }; } ", + r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } } + fn main() { foo::Foo::Bar { bar: () }; } ", + ); + check_assist( + fix_visibility, + r"//- /lib.rs + mod foo; + fn main() { foo::Foo::Bar { <|>bar: () }; } + //- /foo.rs + pub enum Foo { Bar { bar: () } } + ", + r"pub enum Foo { Bar { $0pub(crate) bar: () } } + +", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub struct Foo { pub bar: (), } } + fn main() { foo::Foo { <|>bar: () }; } ", + ); + check_assist_not_applicable( + fix_visibility, + r"//- /lib.rs + mod foo; + fn main() { foo::Foo { <|>bar: () }; } + //- /foo.rs + pub struct Foo { pub bar: () } + ", + ); + } + + #[test] + #[ignore] + // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields + fn fix_visibility_of_union_field() { + check_assist( + fix_visibility, + r"mod foo { pub union Foo { bar: (), } } + fn main() { foo::Foo { <|>bar: () }; } ", + r"mod foo { pub union Foo { $0pub(crate) bar: (), } } + fn main() { foo::Foo { bar: () }; } ", + ); + check_assist( + fix_visibility, + r"//- /lib.rs + mod foo; + fn main() { foo::Foo { <|>bar: () }; } + //- /foo.rs + pub union Foo { bar: () } + ", + r"pub union Foo { $0pub(crate) bar: () } + +", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub union Foo { pub bar: (), } } + fn main() { foo::Foo { <|>bar: () }; } ", + ); + check_assist_not_applicable( + fix_visibility, + r"//- /lib.rs + mod foo; + fn main() { foo::Foo { <|>bar: () }; } + //- /foo.rs + pub union Foo { pub bar: () } + ", + ); + } + + #[test] + fn fix_visibility_of_const() { + check_assist( + fix_visibility, + r"mod foo { const FOO: () = (); } + fn main() { foo::FOO<|> } ", + r"mod foo { $0pub(crate) const FOO: () = (); } + fn main() { foo::FOO } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub const FOO: () = (); } + fn main() { foo::FOO<|> } ", + ); + } + + #[test] + fn fix_visibility_of_static() { + check_assist( + fix_visibility, + r"mod foo { static FOO: () = (); } + fn main() { foo::FOO<|> } ", + r"mod foo { $0pub(crate) static FOO: () = (); } + fn main() { foo::FOO } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub static FOO: () = (); } + fn main() { foo::FOO<|> } ", + ); + } + + #[test] + fn fix_visibility_of_trait() { + check_assist( + fix_visibility, + r"mod foo { trait Foo { fn foo(&self) {} } } + fn main() { let x: &dyn foo::<|>Foo; } ", + r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } } + fn main() { let x: &dyn foo::Foo; } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub trait Foo { fn foo(&self) {} } } + fn main() { let x: &dyn foo::Foo<|>; } ", + ); + } + + #[test] + fn fix_visibility_of_type_alias() { + check_assist( + fix_visibility, + r"mod foo { type Foo = (); } + fn main() { let x: foo::Foo<|>; } ", + r"mod foo { $0pub(crate) type Foo = (); } + fn main() { let x: foo::Foo; } ", + ); + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub type Foo = (); } + fn main() { let x: foo::Foo<|>; } ", + ); + } + + #[test] + fn fix_visibility_of_module() { + check_assist( + fix_visibility, + r"mod foo { mod bar { fn bar() {} } } + fn main() { foo::bar<|>::bar(); } ", + r"mod foo { $0pub(crate) mod bar { fn bar() {} } } + fn main() { foo::bar::bar(); } ", + ); + + check_assist( + fix_visibility, + r" + //- /main.rs + mod foo; + fn main() { foo::bar<|>::baz(); } + + //- /foo.rs + mod bar { + pub fn baz() {} + } + ", + r"$0pub(crate) mod bar { + pub fn baz() {} +} + +", + ); + + check_assist_not_applicable( + fix_visibility, + r"mod foo { pub mod bar { pub fn bar() {} } } + fn main() { foo::bar<|>::bar(); } ", + ); + } + + #[test] + fn fix_visibility_of_inline_module_in_other_file() { + check_assist( + fix_visibility, + r" + //- /main.rs + mod foo; + fn main() { foo::bar<|>::baz(); } + + //- /foo.rs + mod bar; + + //- /foo/bar.rs + pub fn baz() {} + } + ", + r"$0pub(crate) mod bar; +", + ); + } + + #[test] + fn fix_visibility_of_module_declaration_in_other_file() { + check_assist( + fix_visibility, + r"//- /main.rs + mod foo; + fn main() { foo::bar<|>>::baz(); } + + //- /foo.rs + mod bar { + pub fn baz() {} + }", + r"$0pub(crate) mod bar { + pub fn baz() {} +} +", + ); + } + + #[test] + fn adds_pub_when_target_is_in_another_crate() { + check_assist( + fix_visibility, + r"//- /main.rs crate:a deps:foo + foo::Bar<|> + //- /lib.rs crate:foo + struct Bar;", + r"$0pub struct Bar; +", + ) + } + + #[test] + #[ignore] + // FIXME handle reexports properly + fn fix_visibility_of_reexport() { + check_assist( + fix_visibility, + r" + mod foo { + use bar::Baz; + mod bar { pub(super) struct Baz; } + } + foo::Baz<|> + ", + r" + mod foo { + $0pub(crate) use bar::Baz; + mod bar { pub(super) struct Baz; } + } + foo::Baz + ", + ) + } +} diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs index 8030efb35854..5731965766b0 100644 --- a/crates/ra_assists/src/handlers/flip_binexpr.rs +++ b/crates/ra_assists/src/handlers/flip_binexpr.rs @@ -1,6 +1,6 @@ use ra_syntax::ast::{AstNode, BinExpr, BinOp}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: flip_binexpr // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // let _ = 2 + 90; // } // ``` -pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option { +pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let expr = ctx.find_node_at_offset::()?; let lhs = expr.lhs()?.syntax().clone(); let rhs = expr.rhs()?.syntax().clone(); @@ -33,8 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| { - edit.target(op_range); + acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { if let FlipAction::FlipAndReplaceOp(new_op) = action { edit.replace(op_range, new_op); } @@ -69,7 +68,7 @@ impl From for FlipAction { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; #[test] fn flip_binexpr_target_is_the_op() { @@ -86,17 +85,13 @@ mod tests { check_assist( flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", - "fn f() { let res = 2 ==<|> 1; }", + "fn f() { let res = 2 == 1; }", ) } #[test] fn flip_binexpr_works_for_gt() { - check_assist( - flip_binexpr, - "fn f() { let res = 1 ><|> 2; }", - "fn f() { let res = 2 <<|> 1; }", - ) + check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }") } #[test] @@ -104,7 +99,7 @@ mod tests { check_assist( flip_binexpr, "fn f() { let res = 1 <=<|> 2; }", - "fn f() { let res = 2 >=<|> 1; }", + "fn f() { let res = 2 >= 1; }", ) } @@ -113,7 +108,7 @@ mod tests { check_assist( flip_binexpr, "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", - "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", + "fn f() { let res = (2 + 2) == (1 + 1); }", ) } @@ -133,7 +128,7 @@ mod tests { fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { match other.downcast_ref::() { None => false, - Some(it) => self ==<|> it, + Some(it) => self == it, } } "#, diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs index 1dacf29f8342..a57a1c463c44 100644 --- a/crates/ra_assists/src/handlers/flip_comma.rs +++ b/crates/ra_assists/src/handlers/flip_comma.rs @@ -1,6 +1,6 @@ use ra_syntax::{algo::non_trivia_sibling, Direction, T}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: flip_comma // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // ((3, 4), (1, 2)); // } // ``` -pub(crate) fn flip_comma(ctx: AssistCtx) -> Option { +pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let comma = ctx.find_token_at_offset(T![,])?; let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; @@ -28,8 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| { - edit.target(comma.text_range()); + acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { edit.replace(prev.text_range(), next.to_string()); edit.replace(next.text_range(), prev.to_string()); }) @@ -39,14 +38,14 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_target}; + use crate::tests::{check_assist, check_assist_target}; #[test] fn flip_comma_works_for_function_parameters() { check_assist( flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", - "fn foo(y: Result<(), ()>,<|> x: i32) {}", + "fn foo(y: Result<(), ()>, x: i32) {}", ) } diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs index f56769624e7f..0115adc8b53a 100644 --- a/crates/ra_assists/src/handlers/flip_trait_bound.rs +++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs @@ -4,7 +4,7 @@ use ra_syntax::{ Direction, T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: flip_trait_bound // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // fn foo() { } // ``` -pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option { +pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { // We want to replicate the behavior of `flip_binexpr` by only suggesting // the assist when the cursor is on a `+` let plus = ctx.find_token_at_offset(T![+])?; @@ -32,8 +32,8 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option { non_trivia_sibling(plus.clone().into(), Direction::Next)?, ); - ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| { - edit.target(plus.text_range()); + let target = plus.text_range(); + acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { edit.replace(before.text_range(), after.to_string()); edit.replace(after.text_range(), before.to_string()); }) @@ -43,7 +43,7 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; #[test] fn flip_trait_bound_assist_available() { @@ -60,7 +60,7 @@ mod tests { check_assist( flip_trait_bound, "struct S where T: A <|>+ B { }", - "struct S where T: B <|>+ A { }", + "struct S where T: B + A { }", ) } @@ -69,13 +69,13 @@ mod tests { check_assist( flip_trait_bound, "impl X for S where T: A +<|> B { }", - "impl X for S where T: B +<|> A { }", + "impl X for S where T: B + A { }", ) } #[test] fn flip_trait_bound_works_for_fn() { - check_assist(flip_trait_bound, "fn f+ B>(t: T) { }", "fn f+ A>(t: T) { }") + check_assist(flip_trait_bound, "fn f+ B>(t: T) { }", "fn f(t: T) { }") } #[test] @@ -83,7 +83,7 @@ mod tests { check_assist( flip_trait_bound, "fn f(t: T) where T: A +<|> B { }", - "fn f(t: T) where T: B +<|> A { }", + "fn f(t: T) where T: B + A { }", ) } @@ -92,7 +92,7 @@ mod tests { check_assist( flip_trait_bound, "fn f(t: T) where T: A <|>+ 'static { }", - "fn f(t: T) where T: 'static <|>+ A { }", + "fn f(t: T) where T: 'static + A { }", ) } @@ -101,7 +101,7 @@ mod tests { check_assist( flip_trait_bound, "struct S where T: A <|>+ b_mod::B + C { }", - "struct S where T: b_mod::B <|>+ A + C { }", + "struct S where T: b_mod::B + A + C { }", ) } @@ -110,7 +110,7 @@ mod tests { check_assist( flip_trait_bound, "struct S where T: A + B + C + D + E + F +<|> G + H + I + J { }", - "struct S where T: A + B + C + D + E + G +<|> F + H + I + J { }", + "struct S where T: A + B + C + D + E + G + F + H + I + J { }", ) } } diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs index f5702f6e0c22..d26e68847984 100644 --- a/crates/ra_assists/src/handlers/inline_local_variable.rs +++ b/crates/ra_assists/src/handlers/inline_local_variable.rs @@ -3,9 +3,12 @@ use ra_syntax::{ ast::{self, AstNode, AstToken}, TextRange, }; -use test_utils::tested_by; +use test_utils::mark; -use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; // Assist: inline_local_variable // @@ -23,18 +26,18 @@ use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; // (1 + 2) * 4; // } // ``` -pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { +pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let let_stmt = ctx.find_node_at_offset::()?; let bind_pat = match let_stmt.pat()? { ast::Pat::BindPat(pat) => pat, _ => return None, }; if bind_pat.mut_token().is_some() { - tested_by!(test_not_inline_mut_variable); + mark::hit!(test_not_inline_mut_variable); return None; } - if !bind_pat.syntax().text_range().contains_inclusive(ctx.frange.range.start()) { - tested_by!(not_applicable_outside_of_bind_pat); + if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { + mark::hit!(not_applicable_outside_of_bind_pat); return None; } let initializer_expr = let_stmt.initializer()?; @@ -43,7 +46,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { let def = Definition::Local(def); let refs = def.find_usages(ctx.db, None); if refs.is_empty() { - tested_by!(test_not_applicable_if_variable_unused); + mark::hit!(test_not_applicable_if_variable_unused); return None; }; @@ -89,6 +92,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { | (ast::Expr::ParenExpr(_), _) | (ast::Expr::PathExpr(_), _) | (ast::Expr::BlockExpr(_), _) + | (ast::Expr::EffectExpr(_), _) | (_, ast::Expr::CallExpr(_)) | (_, ast::Expr::TupleExpr(_)) | (_, ast::Expr::ArrayExpr(_)) @@ -105,26 +109,21 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { let init_str = initializer_expr.syntax().text().to_string(); let init_in_paren = format!("({})", &init_str); - ctx.add_assist( - AssistId("inline_local_variable"), - "Inline variable", - move |edit: &mut ActionBuilder| { - edit.delete(delete_range); - for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { - let replacement = - if should_wrap { init_in_paren.clone() } else { init_str.clone() }; - edit.replace(desc.file_range.range, replacement) - } - edit.set_cursor(delete_range.start()) - }, - ) + let target = bind_pat.syntax().text_range(); + acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { + builder.delete(delete_range); + for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { + let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; + builder.replace(desc.file_range.range, replacement) + } + }) } #[cfg(test)] mod tests { - use test_utils::covers; + use test_utils::mark; - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -149,7 +148,7 @@ fn foo() { r" fn bar(a: usize) {} fn foo() { - <|>1 + 1; + 1 + 1; if 1 > 10 { } @@ -183,7 +182,7 @@ fn foo() { r" fn bar(a: usize) {} fn foo() { - <|>(1 + 1) + 1; + (1 + 1) + 1; if (1 + 1) > 10 { } @@ -217,7 +216,7 @@ fn foo() { r" fn bar(a: usize) {} fn foo() { - <|>bar(1) + 1; + bar(1) + 1; if bar(1) > 10 { } @@ -251,7 +250,7 @@ fn foo() { r" fn bar(a: usize): usize { a } fn foo() { - <|>(bar(1) as u64) + 1; + (bar(1) as u64) + 1; if (bar(1) as u64) > 10 { } @@ -283,7 +282,7 @@ fn foo() { }", r" fn foo() { - <|>{ 10 + 1 } + 1; + { 10 + 1 } + 1; if { 10 + 1 } > 10 { } @@ -315,7 +314,7 @@ fn foo() { }", r" fn foo() { - <|>( 10 + 1 ) + 1; + ( 10 + 1 ) + 1; if ( 10 + 1 ) > 10 { } @@ -330,7 +329,7 @@ fn foo() { #[test] fn test_not_inline_mut_variable() { - covers!(test_not_inline_mut_variable); + mark::check!(test_not_inline_mut_variable); check_assist_not_applicable( inline_local_variable, r" @@ -353,7 +352,7 @@ fn foo() { }", r" fn foo() { - <|>let b = bar(10 + 1) * 10; + let b = bar(10 + 1) * 10; let c = bar(10 + 1) as usize; }", ); @@ -373,7 +372,7 @@ fn foo() { r" fn foo() { let x = vec![1, 2, 3]; - <|>let b = x[0] * 10; + let b = x[0] * 10; let c = x[0] as usize; }", ); @@ -393,7 +392,7 @@ fn foo() { r" fn foo() { let bar = vec![1]; - <|>let b = bar.len() * 10; + let b = bar.len() * 10; let c = bar.len() as usize; }", ); @@ -421,7 +420,7 @@ struct Bar { fn foo() { let bar = Bar { foo: 1 }; - <|>let b = bar.foo * 10; + let b = bar.foo * 10; let c = bar.foo as usize; }", ); @@ -442,7 +441,7 @@ fn foo() -> Option { r" fn foo() -> Option { let bar = Some(1); - <|>let b = bar? * 10; + let b = bar? * 10; let c = bar? as usize; None }", @@ -462,7 +461,7 @@ fn foo() { r" fn foo() { let bar = 10; - <|>let b = &bar * 10; + let b = &bar * 10; }", ); } @@ -478,7 +477,7 @@ fn foo() { }", r" fn foo() { - <|>let b = (10, 20)[0]; + let b = (10, 20)[0]; }", ); } @@ -494,7 +493,7 @@ fn foo() { }", r" fn foo() { - <|>let b = [1, 2, 3].len(); + let b = [1, 2, 3].len(); }", ); } @@ -511,7 +510,7 @@ fn foo() { }", r" fn foo() { - <|>let b = (10 + 20) * 10; + let b = (10 + 20) * 10; let c = (10 + 20) as usize; }", ); @@ -531,7 +530,7 @@ fn foo() { r" fn foo() { let d = 10; - <|>let b = d * 10; + let b = d * 10; let c = d as usize; }", ); @@ -549,7 +548,7 @@ fn foo() { }", r" fn foo() { - <|>let b = { 10 } * 10; + let b = { 10 } * 10; let c = { 10 } as usize; }", ); @@ -569,7 +568,7 @@ fn foo() { }", r" fn foo() { - <|>let b = (10 + 20) * 10; + let b = (10 + 20) * 10; let c = (10 + 20, 20); let d = [10 + 20, 10]; let e = (10 + 20); @@ -588,7 +587,7 @@ fn foo() { }", r" fn foo() { - <|>for i in vec![10, 20] {} + for i in vec![10, 20] {} }", ); } @@ -604,7 +603,7 @@ fn foo() { }", r" fn foo() { - <|>while 1 > 0 {} + while 1 > 0 {} }", ); } @@ -622,7 +621,7 @@ fn foo() { }", r" fn foo() { - <|>loop { + loop { break 1 + 1; } }", @@ -640,7 +639,7 @@ fn foo() { }", r" fn foo() { - <|>return 1 > 0; + return 1 > 0; }", ); } @@ -656,14 +655,14 @@ fn foo() { }", r" fn foo() { - <|>match 1 > 0 {} + match 1 > 0 {} }", ); } #[test] fn test_not_applicable_if_variable_unused() { - covers!(test_not_applicable_if_variable_unused); + mark::check!(test_not_applicable_if_variable_unused); check_assist_not_applicable( inline_local_variable, r" @@ -676,7 +675,7 @@ fn foo() { #[test] fn not_applicable_outside_of_bind_pat() { - covers!(not_applicable_outside_of_bind_pat); + mark::check!(not_applicable_outside_of_bind_pat); check_assist_not_applicable( inline_local_variable, r" diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs index eda9ac296304..31d6539f7a77 100644 --- a/crates/ra_assists/src/handlers/introduce_variable.rs +++ b/crates/ra_assists/src/handlers/introduce_variable.rs @@ -4,12 +4,12 @@ use ra_syntax::{ BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, WHITESPACE, }, - SyntaxNode, TextSize, + SyntaxNode, }; use stdx::format_to; -use test_utils::tested_by; +use test_utils::mark; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: introduce_variable // @@ -23,17 +23,17 @@ use crate::{Assist, AssistCtx, AssistId}; // -> // ``` // fn main() { -// let var_name = (1 + 2); +// let $0var_name = (1 + 2); // var_name * 4; // } // ``` -pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { +pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { if ctx.frange.range.is_empty() { return None; } let node = ctx.covering_element(); if node.kind() == COMMENT { - tested_by!(introduce_var_in_comment_is_not_applicable); + mark::hit!(introduce_var_in_comment_is_not_applicable); return None; } let expr = node.ancestors().find_map(valid_target_expr)?; @@ -42,17 +42,17 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { if indent.kind() != WHITESPACE { return None; } - ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| { + let target = expr.syntax().text_range(); + acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { let mut buf = String::new(); - let cursor_offset = if wrap_in_block { + if wrap_in_block { buf.push_str("{ let var_name = "); - TextSize::of("{ let ") } else { buf.push_str("let var_name = "); - TextSize::of("let ") }; format_to!(buf, "{}", expr.syntax()); + let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); let is_full_stmt = if let Some(expr_stmt) = &full_stmt { Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) @@ -60,33 +60,47 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { false }; if is_full_stmt { - tested_by!(test_introduce_var_expr_stmt); + mark::hit!(test_introduce_var_expr_stmt); if full_stmt.unwrap().semicolon_token().is_none() { buf.push_str(";"); } - edit.replace(expr.syntax().text_range(), buf); - } else { - buf.push_str(";"); - - // We want to maintain the indent level, - // but we do not want to duplicate possible - // extra newlines in the indent block - let text = indent.text(); - if text.starts_with('\n') { - buf.push_str("\n"); - buf.push_str(text.trim_start_matches('\n')); - } else { - buf.push_str(text); - } - - edit.target(expr.syntax().text_range()); - edit.replace(expr.syntax().text_range(), "var_name".to_string()); - edit.insert(anchor_stmt.text_range().start(), buf); - if wrap_in_block { - edit.insert(anchor_stmt.text_range().end(), " }"); + let offset = expr.syntax().text_range(); + match ctx.config.snippet_cap { + Some(cap) => { + let snip = buf.replace("let var_name", "let $0var_name"); + edit.replace_snippet(cap, offset, snip) + } + None => edit.replace(offset, buf), } + return; + } + + buf.push_str(";"); + + // We want to maintain the indent level, + // but we do not want to duplicate possible + // extra newlines in the indent block + let text = indent.text(); + if text.starts_with('\n') { + buf.push_str("\n"); + buf.push_str(text.trim_start_matches('\n')); + } else { + buf.push_str(text); + } + + edit.replace(expr.syntax().text_range(), "var_name".to_string()); + let offset = anchor_stmt.text_range().start(); + match ctx.config.snippet_cap { + Some(cap) => { + let snip = buf.replace("let var_name", "let $0var_name"); + edit.insert_snippet(cap, offset, snip) + } + None => edit.insert(offset, buf), + } + + if wrap_in_block { + edit.insert(anchor_stmt.text_range().end(), " }"); } - edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); }) } @@ -111,9 +125,9 @@ fn valid_target_expr(node: SyntaxNode) -> Option { /// expression like a lambda or match arm. fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { expr.syntax().ancestors().find_map(|node| { - if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { + if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { if expr.syntax() == &node { - tested_by!(test_introduce_var_last_expr); + mark::hit!(test_introduce_var_last_expr); return Some((node, false)); } } @@ -134,9 +148,9 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { #[cfg(test)] mod tests { - use test_utils::covers; + use test_utils::mark; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use super::*; @@ -144,37 +158,37 @@ mod tests { fn test_introduce_var_simple() { check_assist( introduce_variable, - " + r#" fn foo() { foo(<|>1 + 1<|>); -}", - " +}"#, + r#" fn foo() { - let <|>var_name = 1 + 1; + let $0var_name = 1 + 1; foo(var_name); -}", +}"#, ); } #[test] fn introduce_var_in_comment_is_not_applicable() { - covers!(introduce_var_in_comment_is_not_applicable); + mark::check!(introduce_var_in_comment_is_not_applicable); check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); } #[test] fn test_introduce_var_expr_stmt() { - covers!(test_introduce_var_expr_stmt); + mark::check!(test_introduce_var_expr_stmt); check_assist( introduce_variable, - " + r#" fn foo() { <|>1 + 1<|>; -}", - " +}"#, + r#" fn foo() { - let <|>var_name = 1 + 1; -}", + let $0var_name = 1 + 1; +}"#, ); check_assist( introduce_variable, @@ -185,7 +199,7 @@ fn foo() { }", " fn foo() { - let <|>var_name = { let x = 0; x }; + let $0var_name = { let x = 0; x }; something_else(); }", ); @@ -201,7 +215,7 @@ fn foo() { }", " fn foo() { - let <|>var_name = 1; + let $0var_name = 1; var_name + 1; }", ); @@ -209,7 +223,7 @@ fn foo() { #[test] fn test_introduce_var_last_expr() { - covers!(test_introduce_var_last_expr); + mark::check!(test_introduce_var_last_expr); check_assist( introduce_variable, " @@ -218,7 +232,7 @@ fn foo() { }", " fn foo() { - let <|>var_name = 1 + 1; + let $0var_name = 1 + 1; bar(var_name) }", ); @@ -230,7 +244,7 @@ fn foo() { }", " fn foo() { - let <|>var_name = bar(1 + 1); + let $0var_name = bar(1 + 1); var_name }", ) @@ -253,7 +267,7 @@ fn main() { fn main() { let x = true; let tuple = match x { - true => { let <|>var_name = 2 + 2; (var_name, true) } + true => { let $0var_name = 2 + 2; (var_name, true) } _ => (0, false) }; } @@ -283,7 +297,7 @@ fn main() { let tuple = match x { true => { let y = 1; - let <|>var_name = 2 + y; + let $0var_name = 2 + y; (var_name, true) } _ => (0, false) @@ -304,7 +318,7 @@ fn main() { ", " fn main() { - let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; + let lambda = |x: u32| { let $0var_name = x * 2; var_name }; } ", ); @@ -321,7 +335,7 @@ fn main() { ", " fn main() { - let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; + let lambda = |x: u32| { let $0var_name = x * 2; var_name }; } ", ); @@ -338,7 +352,7 @@ fn main() { ", " fn main() { - let <|>var_name = Some(true); + let $0var_name = Some(true); let o = var_name; } ", @@ -356,7 +370,7 @@ fn main() { ", " fn main() { - let <|>var_name = bar.foo(); + let $0var_name = bar.foo(); let v = var_name; } ", @@ -374,7 +388,7 @@ fn foo() -> u32 { ", " fn foo() -> u32 { - let <|>var_name = 2 + 2; + let $0var_name = 2 + 2; return var_name; } ", @@ -396,7 +410,7 @@ fn foo() -> u32 { fn foo() -> u32 { - let <|>var_name = 2 + 2; + let $0var_name = 2 + 2; return var_name; } ", @@ -413,7 +427,7 @@ fn foo() -> u32 { " fn foo() -> u32 { - let <|>var_name = 2 + 2; + let $0var_name = 2 + 2; return var_name; } ", @@ -438,7 +452,7 @@ fn foo() -> u32 { // bar - let <|>var_name = 2 + 2; + let $0var_name = 2 + 2; return var_name; } ", @@ -459,7 +473,7 @@ fn main() { " fn main() { let result = loop { - let <|>var_name = 2 + 2; + let $0var_name = 2 + 2; break var_name; }; } @@ -478,7 +492,7 @@ fn main() { ", " fn main() { - let <|>var_name = 0f32 as u32; + let $0var_name = 0f32 as u32; let v = var_name; } ", diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs index 682e085120a3..59d278eb9bed 100644 --- a/crates/ra_assists/src/handlers/invert_if.rs +++ b/crates/ra_assists/src/handlers/invert_if.rs @@ -3,7 +3,11 @@ use ra_syntax::{ T, }; -use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + utils::invert_boolean_expression, + AssistId, +}; // Assist: invert_if // @@ -24,7 +28,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; // } // ``` -pub(crate) fn invert_if(ctx: AssistCtx) -> Option { +pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_keyword = ctx.find_token_at_offset(T![if])?; let expr = ast::IfExpr::cast(if_keyword.parent())?; let if_range = if_keyword.text_range(); @@ -40,36 +44,35 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option { let cond = expr.condition()?.expr()?; let then_node = expr.then_branch()?.syntax().clone(); + let else_block = match expr.else_branch()? { + ast::ElseBranch::Block(it) => it, + ast::ElseBranch::IfExpr(_) => return None, + }; - if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { - let cond_range = cond.syntax().text_range(); - let flip_cond = invert_boolean_expression(cond); - let else_node = else_block.syntax(); - let else_range = else_node.text_range(); - let then_range = then_node.text_range(); - return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| { - edit.target(if_range); - edit.replace(cond_range, flip_cond.syntax().text()); - edit.replace(else_range, then_node.text()); - edit.replace(then_range, else_node.text()); - }); - } - - None + let cond_range = cond.syntax().text_range(); + let flip_cond = invert_boolean_expression(cond); + let else_node = else_block.syntax(); + let else_range = else_node.text_range(); + let then_range = then_node.text_range(); + acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| { + edit.replace(cond_range, flip_cond.syntax().text()); + edit.replace(else_range, then_node.text()); + edit.replace(then_range, else_node.text()); + }) } #[cfg(test)] mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; #[test] fn invert_if_remove_inequality() { check_assist( invert_if, "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", - "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", + "fn f() { if x == 3 { 3 + 2 } else { 1 } }", ) } @@ -78,7 +81,7 @@ mod tests { check_assist( invert_if, "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", - "fn f() { <|>if cond { 1 } else { 3 * 2 } }", + "fn f() { if cond { 1 } else { 3 * 2 } }", ) } @@ -87,7 +90,7 @@ mod tests { check_assist( invert_if, "fn f() { i<|>f cond { 3 * 2 } else { 1 } }", - "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }", + "fn f() { if !cond { 1 } else { 3 * 2 } }", ) } diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs index 4be1238f146d..972d16241946 100644 --- a/crates/ra_assists/src/handlers/merge_imports.rs +++ b/crates/ra_assists/src/handlers/merge_imports.rs @@ -6,7 +6,10 @@ use ra_syntax::{ AstNode, Direction, InsertPosition, SyntaxElement, T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; // Assist: merge_imports // @@ -20,10 +23,10 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // use std::{fmt::Formatter, io}; // ``` -pub(crate) fn merge_imports(ctx: AssistCtx) -> Option { +pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let tree: ast::UseTree = ctx.find_node_at_offset()?; let mut rewriter = SyntaxRewriter::default(); - let mut offset = ctx.frange.range.start(); + let mut offset = ctx.offset(); if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { let (merged, to_delete) = next_prev() @@ -52,10 +55,9 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option { } }; - ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { - edit.rewrite(rewriter); - // FIXME: we only need because our diff is imprecise - edit.set_cursor(offset); + let target = tree.syntax().text_range(); + acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { + builder.rewrite(rewriter); }) } @@ -125,7 +127,7 @@ fn first_path(path: &ast::Path) -> ast::Path { #[cfg(test)] mod tests { - use crate::helpers::check_assist; + use crate::tests::check_assist; use super::*; @@ -138,7 +140,7 @@ use std::fmt<|>::Debug; use std::fmt::Display; ", r" -use std::fmt<|>::{Debug, Display}; +use std::fmt::{Debug, Display}; ", ) } @@ -152,7 +154,7 @@ use std::fmt::Debug; use std::fmt<|>::Display; ", r" -use std::fmt:<|>:{Display, Debug}; +use std::fmt::{Display, Debug}; ", ); } @@ -165,7 +167,7 @@ use std::fmt:<|>:{Display, Debug}; use std::{fmt<|>::Debug, fmt::Display}; ", r" -use std::{fmt<|>::{Debug, Display}}; +use std::{fmt::{Debug, Display}}; ", ); check_assist( @@ -174,7 +176,7 @@ use std::{fmt<|>::{Debug, Display}}; use std::{fmt::Debug, fmt<|>::Display}; ", r" -use std::{fmt::<|>{Display, Debug}}; +use std::{fmt::{Display, Debug}}; ", ); } @@ -188,7 +190,7 @@ use std<|>::cell::*; use std::str; ", r" -use std<|>::{cell::*, str}; +use std::{cell::*, str}; ", ) } @@ -202,7 +204,7 @@ use std<|>::cell::*; use std::str::*; ", r" -use std<|>::{cell::*, str::*}; +use std::{cell::*, str::*}; ", ) } @@ -218,7 +220,7 @@ use foo::baz; /// Doc comment ", r" -use foo<|>::{bar, baz}; +use foo::{bar, baz}; /// Doc comment ", @@ -237,7 +239,7 @@ use { ", r" use { - foo<|>::{bar, baz}, + foo::{bar, baz}, }; ", ); @@ -251,7 +253,7 @@ use { ", r" use { - foo::{bar<|>, baz}, + foo::{bar, baz}, }; ", ); @@ -268,7 +270,7 @@ use foo::<|>{ }; ", r" -use foo::{<|> +use foo::{ FooBar, bar::baz}; ", diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs index 5a77d3dbceb5..ca04ec671a0f 100644 --- a/crates/ra_assists/src/handlers/merge_match_arms.rs +++ b/crates/ra_assists/src/handlers/merge_match_arms.rs @@ -3,10 +3,10 @@ use std::iter::successors; use ra_syntax::{ algo::neighbor, ast::{self, AstNode}, - Direction, TextSize, + Direction, }; -use crate::{Assist, AssistCtx, AssistId, TextRange}; +use crate::{AssistContext, AssistId, Assists, TextRange}; // Assist: merge_match_arms // @@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId, TextRange}; // } // } // ``` -pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { +pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let current_arm = ctx.find_node_at_offset::()?; // Don't try to handle arms with guards for now - can add support for this later if current_arm.guard().is_some() { @@ -41,17 +41,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { let current_expr = current_arm.expr()?; let current_text_range = current_arm.syntax().text_range(); - enum CursorPos { - InExpr(TextSize), - InPat(TextSize), - } - let cursor_pos = ctx.frange.range.start(); - let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { - CursorPos::InExpr(current_text_range.end() - cursor_pos) - } else { - CursorPos::InPat(cursor_pos) - }; - // We check if the following match arms match this one. We could, but don't, // compare to the previous match arm as well. let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next)) @@ -70,7 +59,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { + acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { let pats = if arms_to_merge.iter().any(contains_placeholder) { "_".into() } else { @@ -87,11 +76,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { let start = arms_to_merge.first().unwrap().syntax().text_range().start(); let end = arms_to_merge.last().unwrap().syntax().text_range().end(); - edit.target(current_text_range); - edit.set_cursor(match cursor_pos { - CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset, - CursorPos::InPat(offset) => offset, - }); edit.replace(TextRange::new(start, end), arm); }) } @@ -105,7 +89,7 @@ fn contains_placeholder(a: &ast::MatchArm) -> bool { #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -133,7 +117,7 @@ mod tests { fn main() { let x = X::A; let y = match x { - X::A | X::B => { 1i32<|> } + X::A | X::B => { 1i32 } X::C => { 2i32 } } } @@ -165,7 +149,7 @@ mod tests { fn main() { let x = X::A; let y = match x { - X::A | X::B | X::C | X::D => {<|> 1i32 }, + X::A | X::B | X::C | X::D => { 1i32 }, X::E => { 2i32 }, } } @@ -198,7 +182,7 @@ mod tests { let x = X::A; let y = match x { X::A => { 1i32 }, - _ => { 2i<|>32 } + _ => { 2i32 } } } "#, @@ -227,7 +211,7 @@ mod tests { fn main() { match X::A { - X::A<|> | X::B | X::C => 92, + X::A | X::B | X::C => 92, X::D => 62, _ => panic!(), } diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs index 0f26884dc13e..be2a7eddcfad 100644 --- a/crates/ra_assists/src/handlers/move_bounds.rs +++ b/crates/ra_assists/src/handlers/move_bounds.rs @@ -5,7 +5,7 @@ use ra_syntax::{ T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: move_bounds_to_where_clause // @@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId}; // f(x) // } // ``` -pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option { +pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let type_param_list = ctx.find_node_at_offset::()?; let mut type_params = type_param_list.type_params(); @@ -49,7 +49,8 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option { } }; - ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |edit| { + let target = type_param_list.syntax().text_range(); + acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| { let new_params = type_param_list .type_params() .filter(|it| it.type_bound_list().is_some()) @@ -71,7 +72,6 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option { _ => format!(" {}", where_clause.syntax()), }; edit.insert(anchor.text_range().start(), to_insert); - edit.target(type_param_list.syntax().text_range()); }) } @@ -89,7 +89,7 @@ fn build_predicate(param: ast::TypeParam) -> Option { mod tests { use super::*; - use crate::helpers::check_assist; + use crate::tests::check_assist; #[test] fn move_bounds_to_where_clause_fn() { @@ -99,7 +99,7 @@ mod tests { fn fooF: FnOnce(T) -> T>() {} "#, r#" - fn fooF>() where T: u32, F: FnOnce(T) -> T {} + fn foo() where T: u32, F: FnOnce(T) -> T {} "#, ); } @@ -112,7 +112,7 @@ mod tests { implT> A {} "#, r#" - implT> A where U: u32 {} + impl A where U: u32 {} "#, ); } @@ -125,7 +125,7 @@ mod tests { struct A<<|>T: Iterator> {} "#, r#" - struct A<<|>T> where T: Iterator {} + struct A where T: Iterator {} "#, ); } @@ -138,7 +138,7 @@ mod tests { struct Pair<<|>T: u32>(T, T); "#, r#" - struct Pair<<|>T>(T, T) where T: u32; + struct Pair(T, T) where T: u32; "#, ); } diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs index d5ccdd91cefa..7edcf0748958 100644 --- a/crates/ra_assists/src/handlers/move_guard.rs +++ b/crates/ra_assists/src/handlers/move_guard.rs @@ -1,10 +1,9 @@ use ra_syntax::{ - ast, - ast::{AstNode, AstToken, IfExpr, MatchArm}, - TextSize, + ast::{AstNode, IfExpr, MatchArm}, + SyntaxKind::WHITESPACE, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: move_guard_to_arm_body // @@ -31,7 +30,7 @@ use crate::{Assist, AssistCtx, AssistId}; // } // } // ``` -pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { +pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let match_arm = ctx.find_node_at_offset::()?; let guard = match_arm.guard()?; let space_before_guard = guard.syntax().prev_sibling_or_token(); @@ -40,26 +39,17 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { let arm_expr = match_arm.expr()?; let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); - ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| { - edit.target(guard.syntax().text_range()); - let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { - Some(tok) => { - if ast::Whitespace::cast(tok.clone()).is_some() { - let ele = tok.text_range(); - edit.delete(ele); - ele.len() - } else { - TextSize::from(0) - } + let target = guard.syntax().text_range(); + acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { + match space_before_guard { + Some(element) if element.kind() == WHITESPACE => { + edit.delete(element.text_range()); } - _ => TextSize::from(0), + _ => (), }; edit.delete(guard.syntax().text_range()); edit.replace_node_and_indent(arm_expr.syntax(), buf); - edit.set_cursor( - arm_expr.syntax().text_range().start() + TextSize::from(3) - offseting_amount, - ); }) } @@ -88,7 +78,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { // } // } // ``` -pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { +pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let match_arm: MatchArm = ctx.find_node_at_offset::()?; let match_pat = match_arm.pat()?; @@ -108,14 +98,15 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { let buf = format!(" if {}", cond.syntax().text()); - ctx.add_assist( + let target = if_expr.syntax().text_range(); + acc.add( AssistId("move_arm_cond_to_match_guard"), "Move condition to match guard", + target, |edit| { - edit.target(if_expr.syntax().text_range()); - let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none(); + let then_only_expr = then_block.statements().next().is_none(); - match &then_block.block().and_then(|it| it.expr()) { + match &then_block.expr() { Some(then_expr) if then_only_expr => { edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) } @@ -123,7 +114,6 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { } edit.insert(match_pat.syntax().text_range().end(), buf); - edit.set_cursor(match_pat.syntax().text_range().end() + TextSize::from(1)); }, ) } @@ -132,7 +122,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; #[test] fn move_guard_to_arm_body_target() { @@ -171,7 +161,7 @@ mod tests { let t = 'a'; let chars = "abcd"; match t { - '\r' => if chars.clone().next() == Some('\n') { <|>false }, + '\r' => if chars.clone().next() == Some('\n') { false }, _ => true } } @@ -194,7 +184,7 @@ mod tests { r#" fn f() { match x { - y @ 4 | y @ 5 => if y > 5 { <|>true }, + y @ 4 | y @ 5 => if y > 5 { true }, _ => false } } @@ -221,7 +211,7 @@ mod tests { let t = 'a'; let chars = "abcd"; match t { - '\r' <|>if chars.clone().next() == Some('\n') => false, + '\r' if chars.clone().next() == Some('\n') => false, _ => true } } @@ -265,7 +255,7 @@ mod tests { let t = 'a'; let chars = "abcd"; match t { - '\r' <|>if chars.clone().next().is_some() => { }, + '\r' if chars.clone().next().is_some() => { }, _ => true } } @@ -295,7 +285,7 @@ mod tests { let mut t = 'a'; let chars = "abcd"; match t { - '\r' <|>if chars.clone().next().is_some() => { + '\r' if chars.clone().next().is_some() => { t = 'e'; false }, diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 567400b9c274..16002d2acec5 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs @@ -5,7 +5,7 @@ use ra_syntax::{ TextSize, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: make_raw_string // @@ -22,11 +22,11 @@ use crate::{Assist, AssistCtx, AssistId}; // r#"Hello, World!"#; // } // ``` -pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { +pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; let value = token.value()?; - ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { - edit.target(token.syntax().text_range()); + let target = token.syntax().text_range(); + acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { let max_hash_streak = count_hashes(&value); let mut hashes = String::with_capacity(max_hash_streak + 1); for _ in 0..hashes.capacity() { @@ -51,11 +51,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { // "Hello, \"World!\""; // } // ``` -pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option { +pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; let value = token.value()?; - ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { - edit.target(token.syntax().text_range()); + let target = token.syntax().text_range(); + acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { // parse inside string to escape `"` let escaped = value.escape_default().to_string(); edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); @@ -77,10 +77,10 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option { // r##"Hello, World!"##; // } // ``` -pub(crate) fn add_hash(ctx: AssistCtx) -> Option { +pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(RAW_STRING)?; - ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { - edit.target(token.text_range()); + let target = token.text_range(); + acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| { edit.insert(token.text_range().start() + TextSize::of('r'), "#"); edit.insert(token.text_range().end(), "#"); }) @@ -101,15 +101,15 @@ pub(crate) fn add_hash(ctx: AssistCtx) -> Option { // r"Hello, World!"; // } // ``` -pub(crate) fn remove_hash(ctx: AssistCtx) -> Option { +pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(RAW_STRING)?; let text = token.text().as_str(); if text.starts_with("r\"") { // no hash to remove return None; } - ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| { - edit.target(token.text_range()); + let target = token.text_range(); + acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { let result = &text[2..text.len() - 1]; let result = if result.starts_with('\"') { // FIXME: this logic is wrong, not only the last has has to handled specially @@ -138,7 +138,7 @@ fn count_hashes(s: &str) -> usize { #[cfg(test)] mod test { use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; #[test] fn make_raw_string_target() { @@ -164,7 +164,7 @@ mod test { "#, r##" fn f() { - let s = <|>r#"random + let s = r#"random string"#; } "##, @@ -182,7 +182,7 @@ string"#; "#, r##" fn f() { - format!(<|>r#"x = {}"#, 92) + format!(r#"x = {}"#, 92) } "##, ) @@ -199,7 +199,7 @@ string"#; "###, r####" fn f() { - let s = <|>r#"#random## + let s = r#"#random## string"#; } "####, @@ -217,7 +217,7 @@ string"#; "###, r####" fn f() { - let s = <|>r###"#random"## + let s = r###"#random"## string"###; } "####, @@ -235,7 +235,7 @@ string"###; "#, r##" fn f() { - let s = <|>r#"random string"#; + let s = r#"random string"#; } "##, ) @@ -289,7 +289,7 @@ string"###; "#, r##" fn f() { - let s = <|>r#"random string"#; + let s = r#"random string"#; } "##, ) @@ -306,7 +306,7 @@ string"###; "##, r###" fn f() { - let s = <|>r##"random"string"##; + let s = r##"random"string"##; } "###, ) @@ -348,7 +348,7 @@ string"###; "##, r#" fn f() { - let s = <|>r"random string"; + let s = r"random string"; } "#, ) @@ -365,7 +365,7 @@ string"###; "##, r#" fn f() { - let s = <|>r"random\"str\"ing"; + let s = r"random\"str\"ing"; } "#, ) @@ -382,7 +382,7 @@ string"###; "###, r##" fn f() { - let s = <|>r#"random string"#; + let s = r#"random string"#; } "##, ) @@ -436,7 +436,7 @@ string"###; "##, r#" fn f() { - let s = <|>"random string"; + let s = "random string"; } "#, ) @@ -453,7 +453,7 @@ string"###; "##, r#" fn f() { - let s = <|>"random\"str\"ing"; + let s = "random\"str\"ing"; } "#, ) @@ -470,7 +470,7 @@ string"###; "###, r##" fn f() { - let s = <|>"random string"; + let s = "random string"; } "##, ) diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs index 4e5eb4350526..961ee1731ad0 100644 --- a/crates/ra_assists/src/handlers/remove_dbg.rs +++ b/crates/ra_assists/src/handlers/remove_dbg.rs @@ -3,7 +3,7 @@ use ra_syntax::{ TextSize, T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: remove_dbg // @@ -20,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId}; // 92; // } // ``` -pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { +pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let macro_call = ctx.find_node_at_offset::()?; if !is_valid_macrocall(¯o_call, "dbg")? { @@ -29,26 +29,6 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { let macro_range = macro_call.syntax().text_range(); - // If the cursor is inside the macro call, we'll try to maintain the cursor - // position by subtracting the length of dbg!( from the start of the file - // range, otherwise we'll default to using the start of the macro call - let cursor_pos = { - let file_range = ctx.frange.range; - - let offset_start = file_range - .start() - .checked_sub(macro_range.start()) - .unwrap_or_else(|| TextSize::from(0)); - - let dbg_size = TextSize::of("dbg!("); - - if offset_start > dbg_size { - file_range.start() - dbg_size - } else { - macro_range.start() - } - }; - let macro_content = { let macro_args = macro_call.token_tree()?.syntax().clone(); @@ -57,10 +37,9 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { text.slice(without_parens).to_string() }; - ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { - edit.target(macro_call.syntax().text_range()); - edit.replace(macro_range, macro_content); - edit.set_cursor(cursor_pos); + let target = macro_call.syntax().text_range(); + acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| { + builder.replace(macro_range, macro_content); }) } @@ -90,17 +69,17 @@ fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Optiondbg!(1 + 1)", "<|>1 + 1"); + check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1"); - check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); + check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)"); - check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); + check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1"); - check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); + check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1"); check_assist( remove_dbg, @@ -113,7 +92,7 @@ fn foo(n: usize) { ", " fn foo(n: usize) { - if let Some(_) = n.<|>checked_sub(4) { + if let Some(_) = n.checked_sub(4) { // ... } } @@ -122,8 +101,8 @@ fn foo(n: usize) { } #[test] fn test_remove_dbg_with_brackets_and_braces() { - check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); - check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); + check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1"); + check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1"); } #[test] diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs index e598023b2557..fe4eada03408 100644 --- a/crates/ra_assists/src/handlers/remove_mut.rs +++ b/crates/ra_assists/src/handlers/remove_mut.rs @@ -1,6 +1,6 @@ use ra_syntax::{SyntaxKind, TextRange, T}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: remove_mut // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // fn feed(&self, amount: u32) {} // } // ``` -pub(crate) fn remove_mut(ctx: AssistCtx) -> Option { +pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let mut_token = ctx.find_token_at_offset(T![mut])?; let delete_from = mut_token.text_range().start(); let delete_to = match mut_token.next_token() { @@ -25,8 +25,8 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option { _ => mut_token.text_range().end(), }; - ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", |edit| { - edit.set_cursor(delete_from); - edit.delete(TextRange::new(delete_from, delete_to)); + let target = mut_token.text_range(); + acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| { + builder.delete(TextRange::new(delete_from, delete_to)); }) } diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 5cbb98d73fdc..30229edc2f24 100644 --- a/crates/ra_assists/src/handlers/reorder_fields.rs +++ b/crates/ra_assists/src/handlers/reorder_fields.rs @@ -3,18 +3,9 @@ use std::collections::HashMap; use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; use itertools::Itertools; use ra_ide_db::RootDatabase; -use ra_syntax::{ - algo, - ast::{self, Path, RecordLit, RecordPat}, - match_ast, AstNode, SyntaxKind, - SyntaxKind::*, - SyntaxNode, -}; +use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - AssistId, -}; +use crate::{AssistContext, AssistId, Assists}; // Assist: reorder_fields // @@ -31,13 +22,13 @@ use crate::{ // const test: Foo = Foo {foo: 1, bar: 0} // ``` // -pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option { - reorder::(ctx.clone()).or_else(|| reorder::(ctx)) +pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + reorder::(acc, ctx.clone()).or_else(|| reorder::(acc, ctx)) } -fn reorder(ctx: AssistCtx) -> Option { +fn reorder(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let record = ctx.find_node_at_offset::()?; - let path = record.syntax().children().find_map(Path::cast)?; + let path = record.syntax().children().find_map(ast::Path::cast)?; let ranks = compute_fields_ranks(&path, &ctx)?; @@ -50,11 +41,11 @@ fn reorder(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { + let target = record.syntax().text_range(); + acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { for (old, new) in fields.iter().zip(&sorted_fields) { algo::diff(old, new).into_text_edit(edit.text_edit_builder()); } - edit.target(record.syntax().text_range()) }) } @@ -96,9 +87,9 @@ fn struct_definition(path: &ast::Path, sema: &Semantics) -> Option } } -fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option> { +fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option> { Some( - struct_definition(path, ctx.sema)? + struct_definition(path, &ctx.sema)? .fields(ctx.db) .iter() .enumerate() @@ -109,7 +100,7 @@ fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> OptionFoo {foo: 1, bar: 0} + const test: Foo = Foo {foo: 1, bar: 0} "#, ) } @@ -173,7 +164,7 @@ mod tests { fn f(f: Foo) -> { match f { - <|>Foo { ref mut bar, baz: 0, .. } => (), + Foo { ref mut bar, baz: 0, .. } => (), _ => () } } @@ -211,7 +202,7 @@ mod tests { impl Foo { fn new() -> Foo { let foo = String::new(); - <|>Foo { + Foo { foo, bar: foo.clone(), extra: "Extra field", diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs index 9841f6980bc3..e016f51c3eb4 100644 --- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs @@ -1,10 +1,14 @@ use ra_fmt::unwrap_trivial_block; use ra_syntax::{ - ast::{self, edit::IndentLevel, make}, + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, + }, AstNode, }; -use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; +use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; // Assist: replace_if_let_with_match // @@ -32,7 +36,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; // } // } // ``` -pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { +pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; let cond = if_expr.condition()?; let pat = cond.pat()?; @@ -43,29 +47,27 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { ast::ElseBranch::IfExpr(_) => return None, }; - let sema = ctx.sema; - ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { + let target = if_expr.syntax().text_range(); + acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { let match_expr = { let then_arm = { let then_expr = unwrap_trivial_block(then_block); make::match_arm(vec![pat.clone()], then_expr) }; let else_arm = { - let pattern = sema + let pattern = ctx + .sema .type_of_pat(&pat) - .and_then(|ty| TryEnum::from_ty(sema, &ty)) + .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) .map(|it| it.sad_pattern()) .unwrap_or_else(|| make::placeholder_pat().into()); let else_expr = unwrap_trivial_block(else_block); make::match_arm(vec![pattern], else_expr) }; make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) + .indent(IndentLevel::from_node(if_expr.syntax())) }; - let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); - - edit.target(if_expr.syntax().text_range()); - edit.set_cursor(if_expr.syntax().text_range().start()); edit.replace_ast::(if_expr.into(), match_expr); }) } @@ -74,13 +76,13 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { mod tests { use super::*; - use crate::helpers::{check_assist, check_assist_target}; + use crate::tests::{check_assist, check_assist_target}; #[test] fn test_replace_if_let_with_match_unwraps_simple_expressions() { check_assist( replace_if_let_with_match, - " + r#" impl VariantData { pub fn is_struct(&self) -> bool { if <|>let VariantData::Struct(..) = *self { @@ -89,16 +91,16 @@ impl VariantData { false } } -} ", - " +} "#, + r#" impl VariantData { pub fn is_struct(&self) -> bool { - <|>match *self { + match *self { VariantData::Struct(..) => true, _ => false, } } -} ", +} "#, ) } @@ -106,7 +108,7 @@ impl VariantData { fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() { check_assist( replace_if_let_with_match, - " + r#" fn foo() { if <|>let VariantData::Struct(..) = a { bar( @@ -115,10 +117,10 @@ fn foo() { } else { false } -} ", - " +} "#, + r#" fn foo() { - <|>match a { + match a { VariantData::Struct(..) => { bar( 123 @@ -126,7 +128,7 @@ fn foo() { } _ => false, } -} ", +} "#, ) } @@ -134,7 +136,7 @@ fn foo() { fn replace_if_let_with_match_target() { check_assist_target( replace_if_let_with_match, - " + r#" impl VariantData { pub fn is_struct(&self) -> bool { if <|>let VariantData::Struct(..) = *self { @@ -143,7 +145,7 @@ impl VariantData { false } } -} ", +} "#, "if let VariantData::Struct(..) = *self { true } else { @@ -173,7 +175,7 @@ enum Option { Some(T), None } use Option::*; fn foo(x: Option) { - <|>match x { + match x { Some(x) => println!("{}", x), None => println!("none"), } @@ -203,7 +205,7 @@ enum Result { Ok(T), Err(E) } use Result::*; fn foo(x: Result) { - <|>match x { + match x { Ok(x) => println!("{}", x), Err(_) => println!("none"), } diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs index 0cf23b754e8d..761557ac05c8 100644 --- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs @@ -9,11 +9,7 @@ use ra_syntax::{ AstNode, T, }; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - utils::TryEnum, - AssistId, -}; +use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; // Assist: replace_let_with_if_let // @@ -39,15 +35,16 @@ use crate::{ // // fn compute() -> Option { None } // ``` -pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option { +pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let let_kw = ctx.find_token_at_offset(T![let])?; let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; let init = let_stmt.initializer()?; let original_pat = let_stmt.pat()?; let ty = ctx.sema.type_of_expr(&init)?; - let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); + let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case()); - ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { + let target = let_kw.text_range(); + acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { let with_placeholder: ast::Pat = match happy_variant { None => make::placeholder_pat().into(), Some(var_name) => make::tuple_struct_pat( @@ -56,25 +53,20 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option { ) .into(), }; - let block = - IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None)); + let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); let stmt = make::expr_stmt(if_); let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); - let target_offset = - let_stmt.syntax().text_range().start() + placeholder.syntax().text_range().start(); let stmt = stmt.replace_descendant(placeholder.into(), original_pat); edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); - edit.target(let_kw.text_range()); - edit.set_cursor(target_offset); }) } #[cfg(test)] mod tests { - use crate::helpers::check_assist; + use crate::tests::check_assist; use super::*; @@ -93,7 +85,7 @@ fn main() { enum E { X(T), Y(T) } fn main() { - if let <|>x = E::X(92) { + if let x = E::X(92) { } } ", diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs index 918e8dd8dae5..0197a8cf0678 100644 --- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs @@ -1,11 +1,7 @@ use hir; use ra_syntax::{ast, AstNode, SmolStr, TextRange}; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - utils::insert_use_statement, - AssistId, -}; +use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; // Assist: replace_qualified_name_with_use // @@ -20,7 +16,10 @@ use crate::{ // // fn process(map: HashMap) {} // ``` -pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option { +pub(crate) fn replace_qualified_name_with_use( + acc: &mut Assists, + ctx: &AssistContext, +) -> Option<()> { let path: ast::Path = ctx.find_node_at_offset()?; // We don't want to mess with use statements if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { @@ -33,17 +32,19 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option return None; } - ctx.add_assist( + let target = path.syntax().text_range(); + acc.add( AssistId("replace_qualified_name_with_use"), "Replace qualified path with use", - |edit| { + target, + |builder| { let path_to_import = hir_path.mod_path().clone(); - insert_use_statement(path.syntax(), &path_to_import, edit.text_edit_builder()); + insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); if let Some(last) = path.segment() { // Here we are assuming the assist will provide a correct use statement // so we can delete the path qualifier - edit.delete(TextRange::new( + builder.delete(TextRange::new( path.syntax().text_range().start(), last.syntax().text_range().start(), )); @@ -74,7 +75,7 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option> { #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; @@ -88,7 +89,7 @@ std::fmt::Debug<|> " use std::fmt::Debug; -Debug<|> +Debug ", ); } @@ -105,7 +106,7 @@ fn main() { " use std::fmt::Debug; -Debug<|> +Debug fn main() { } @@ -129,7 +130,7 @@ use std::fmt::Debug; fn main() { } -Debug<|> +Debug ", ); } @@ -144,7 +145,7 @@ std::fmt<|>::Debug " use std::fmt; -fmt<|>::Debug +fmt::Debug ", ); } @@ -163,7 +164,7 @@ impl std::fmt::Debug<|> for Foo { use stdx; use std::fmt::Debug; -impl Debug<|> for Foo { +impl Debug for Foo { } ", ); @@ -180,7 +181,7 @@ impl std::fmt::Debug<|> for Foo { " use std::fmt::Debug; -impl Debug<|> for Foo { +impl Debug for Foo { } ", ); @@ -197,7 +198,7 @@ impl Debug<|> for Foo { " use std::fmt::Debug; - impl Debug<|> for Foo { + impl Debug for Foo { } ", ); @@ -216,7 +217,7 @@ impl std::io<|> for Foo { " use std::{io, fmt}; -impl io<|> for Foo { +impl io for Foo { } ", ); @@ -235,7 +236,7 @@ impl std::fmt::Debug<|> for Foo { " use std::fmt::{self, Debug, }; -impl Debug<|> for Foo { +impl Debug for Foo { } ", ); @@ -254,7 +255,7 @@ impl std::fmt<|> for Foo { " use std::fmt::{self, Debug}; -impl fmt<|> for Foo { +impl fmt for Foo { } ", ); @@ -273,7 +274,7 @@ impl std::fmt::nested<|> for Foo { " use std::fmt::{Debug, nested::{Display, self}}; -impl nested<|> for Foo { +impl nested for Foo { } ", ); @@ -292,7 +293,7 @@ impl std::fmt::nested<|> for Foo { " use std::fmt::{Debug, nested::{self, Display}}; -impl nested<|> for Foo { +impl nested for Foo { } ", ); @@ -311,7 +312,7 @@ impl std::fmt::nested::Debug<|> for Foo { " use std::fmt::{Debug, nested::{Display, Debug}}; -impl Debug<|> for Foo { +impl Debug for Foo { } ", ); @@ -330,7 +331,7 @@ impl std::fmt::nested::Display<|> for Foo { " use std::fmt::{nested::Display, Debug}; -impl Display<|> for Foo { +impl Display for Foo { } ", ); @@ -349,7 +350,7 @@ impl std::fmt::Display<|> for Foo { " use std::fmt::{Display, nested::Debug}; -impl Display<|> for Foo { +impl Display for Foo { } ", ); @@ -373,7 +374,7 @@ use crate::{ AssocItem, }; -fn foo() { lower<|>::trait_env() } +fn foo() { lower::trait_env() } ", ); } @@ -391,7 +392,7 @@ impl foo::Debug<|> for Foo { " use std::fmt as foo; -impl Debug<|> for Foo { +impl Debug for Foo { } ", ); @@ -434,7 +435,7 @@ mod foo { mod bar { use std::fmt::Debug; - Debug<|> + Debug } } ", @@ -457,7 +458,7 @@ fn main() { use std::fmt::Debug; fn main() { - Debug<|> + Debug } ", ); diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index 62d4ea522025..cff7dfb81215 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs @@ -1,11 +1,18 @@ use std::iter; use ra_syntax::{ - ast::{self, edit::IndentLevel, make}, + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, + }, AstNode, }; -use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; +use crate::{ + utils::{render_snippet, Cursor, TryEnum}, + AssistContext, AssistId, Assists, +}; // Assist: replace_unwrap_with_match // @@ -25,11 +32,11 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; // let x: Result = Result::Ok(92); // let y = match x { // Ok(a) => a, -// _ => unreachable!(), +// $0_ => unreachable!(), // }; // } // ``` -pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option { +pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; let name = method_call.name_ref()?; if name.text() != "unwrap" { @@ -37,9 +44,9 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option { } let caller = method_call.expr()?; let ty = ctx.sema.type_of_expr(&caller)?; - let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); - - ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { + let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); + let target = method_call.syntax().text_range(); + acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| { let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); let it = make::bind_pat(make::name("a")).into(); let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); @@ -47,23 +54,36 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option { let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); - let unreachable_call = make::unreachable_macro_call().into(); + let unreachable_call = make::expr_unreachable(); let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); - let match_expr = make::expr_match(caller.clone(), match_arm_list); - let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); + let match_expr = make::expr_match(caller.clone(), match_arm_list) + .indent(IndentLevel::from_node(method_call.syntax())); - edit.target(method_call.syntax().text_range()); - edit.set_cursor(caller.syntax().text_range().start()); - edit.replace_ast::(method_call.into(), match_expr); + let range = method_call.syntax().text_range(); + match ctx.config.snippet_cap { + Some(cap) => { + let err_arm = match_expr + .syntax() + .descendants() + .filter_map(ast::MatchArm::cast) + .last() + .unwrap(); + let snippet = + render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax())); + builder.replace_snippet(cap, range, snippet) + } + None => builder.replace(range, match_expr.to_string()), + } }) } #[cfg(test)] mod tests { + use crate::tests::{check_assist, check_assist_target}; + use super::*; - use crate::helpers::{check_assist, check_assist_target}; #[test] fn test_replace_result_unwrap_with_match() { @@ -82,9 +102,9 @@ enum Result { Ok(T), Err(E) } fn i(a: T) -> T { a } fn main() { let x: Result = Result::Ok(92); - let y = <|>match i(x) { + let y = match i(x) { Ok(a) => a, - _ => unreachable!(), + $0_ => unreachable!(), }; } ", @@ -108,9 +128,9 @@ enum Option { Some(T), None } fn i(a: T) -> T { a } fn main() { let x = Option::Some(92); - let y = <|>match i(x) { + let y = match i(x) { Some(a) => a, - _ => unreachable!(), + $0_ => unreachable!(), }; } ", @@ -134,9 +154,9 @@ enum Result { Ok(T), Err(E) } fn i(a: T) -> T { a } fn main() { let x: Result = Result::Ok(92); - let y = <|>match i(x) { + let y = match i(x) { Ok(a) => a, - _ => unreachable!(), + $0_ => unreachable!(), }.count_zeroes(); } ", diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index f25826796317..c7a8744802dc 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs @@ -2,7 +2,7 @@ use std::iter::successors; use ra_syntax::{ast, AstNode, T}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: split_import // @@ -15,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // use std::{collections::HashMap}; // ``` -pub(crate) fn split_import(ctx: AssistCtx) -> Option { +pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let colon_colon = ctx.find_token_at_offset(T![::])?; let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; @@ -26,18 +26,16 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option { if new_tree == use_tree { return None; } - let cursor = ctx.frange.range.start(); - ctx.add_assist(AssistId("split_import"), "Split import", |edit| { - edit.target(colon_colon.text_range()); + let target = colon_colon.text_range(); + acc.add(AssistId("split_import"), "Split import", target, |edit| { edit.replace_ast(use_tree, new_tree); - edit.set_cursor(cursor); }) } #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use super::*; @@ -46,7 +44,7 @@ mod tests { check_assist( split_import, "use crate::<|>db::RootDatabase;", - "use crate::<|>{db::RootDatabase};", + "use crate::{db::RootDatabase};", ) } @@ -55,7 +53,7 @@ mod tests { check_assist( split_import, "use crate:<|>:db::{RootDatabase, FileSymbol}", - "use crate:<|>:{db::{RootDatabase, FileSymbol}}", + "use crate::{db::{RootDatabase, FileSymbol}}", ) } diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs new file mode 100644 index 000000000000..8440c7d0f43e --- /dev/null +++ b/crates/ra_assists/src/handlers/unwrap_block.rs @@ -0,0 +1,512 @@ +use ra_fmt::unwrap_trivial_block; +use ra_syntax::{ + ast::{self, ElseBranch, Expr, LoopBodyOwner}, + match_ast, AstNode, TextRange, T, +}; + +use crate::{AssistContext, AssistId, Assists}; + +// Assist: unwrap_block +// +// This assist removes if...else, for, while and loop control statements to just keep the body. +// +// ``` +// fn foo() { +// if true {<|> +// println!("foo"); +// } +// } +// ``` +// -> +// ``` +// fn foo() { +// println!("foo"); +// } +// ``` +pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let l_curly_token = ctx.find_token_at_offset(T!['{'])?; + let block = ast::BlockExpr::cast(l_curly_token.parent())?; + let parent = block.syntax().parent()?; + let assist_id = AssistId("unwrap_block"); + let assist_label = "Unwrap block"; + + let (expr, expr_to_unwrap) = match_ast! { + match parent { + ast::ForExpr(for_expr) => { + let block_expr = for_expr.loop_body()?; + let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; + (ast::Expr::ForExpr(for_expr), expr_to_unwrap) + }, + ast::WhileExpr(while_expr) => { + let block_expr = while_expr.loop_body()?; + let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; + (ast::Expr::WhileExpr(while_expr), expr_to_unwrap) + }, + ast::LoopExpr(loop_expr) => { + let block_expr = loop_expr.loop_body()?; + let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; + (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap) + }, + ast::IfExpr(if_expr) => { + let mut resp = None; + + let then_branch = if_expr.then_branch()?; + if then_branch.l_curly_token()?.text_range().contains_range(ctx.frange.range) { + if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { + // For `else if` blocks + let ancestor_then_branch = ancestor.then_branch()?; + let l_curly_token = then_branch.l_curly_token()?; + + let target = then_branch.syntax().text_range(); + return acc.add(assist_id, assist_label, target, |edit| { + let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); + let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end()); + + edit.delete(range_to_del_rest); + edit.delete(range_to_del_else_if); + edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{'])); + }); + } else { + resp = Some((ast::Expr::IfExpr(if_expr.clone()), Expr::BlockExpr(then_branch))); + } + } else if let Some(else_branch) = if_expr.else_branch() { + match else_branch { + ElseBranch::Block(else_block) => { + let l_curly_token = else_block.l_curly_token()?; + if l_curly_token.text_range().contains_range(ctx.frange.range) { + let target = else_block.syntax().text_range(); + return acc.add(assist_id, assist_label, target, |edit| { + let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); + + edit.delete(range_to_del); + edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{'])); + }); + } + }, + ElseBranch::IfExpr(_) => {}, + } + } + + resp? + }, + _ => return None, + } + }; + + let target = expr_to_unwrap.syntax().text_range(); + acc.add(assist_id, assist_label, target, |edit| { + edit.replace( + expr.syntax().text_range(), + update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']), + ); + }) +} + +fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option { + let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range); + + if cursor_in_range { + Some(unwrap_trivial_block(block)) + } else { + None + } +} + +fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { + let expr_string = expr_str.trim_start_matches(trim_start_pat); + let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); + expr_string_lines.pop(); // Delete last line + + expr_string_lines + .into_iter() + .map(|line| line.replacen(" ", "", 1)) // Delete indentation + .collect::>() + .join("\n") +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn simple_if() { + check_assist( + unwrap_block, + r#" + fn main() { + bar(); + if true {<|> + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + "#, + r#" + fn main() { + bar(); + foo(); + + //comment + bar(); + } + "#, + ); + } + + #[test] + fn simple_if_else() { + check_assist( + unwrap_block, + r#" + fn main() { + bar(); + if true { + foo(); + + //comment + bar(); + } else {<|> + println!("bar"); + } + } + "#, + r#" + fn main() { + bar(); + if true { + foo(); + + //comment + bar(); + } + println!("bar"); + } + "#, + ); + } + + #[test] + fn simple_if_else_if() { + check_assist( + unwrap_block, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false {<|> + println!("bar"); + } else { + println!("foo"); + } + } + "#, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } + println!("bar"); + } + "#, + ); + } + + #[test] + fn simple_if_else_if_nested() { + check_assist( + unwrap_block, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false { + println!("bar"); + } else if true {<|> + println!("foo"); + } + } + "#, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false { + println!("bar"); + } + println!("foo"); + } + "#, + ); + } + + #[test] + fn simple_if_else_if_nested_else() { + check_assist( + unwrap_block, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false { + println!("bar"); + } else if true { + println!("foo"); + } else {<|> + println!("else"); + } + } + "#, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false { + println!("bar"); + } else if true { + println!("foo"); + } + println!("else"); + } + "#, + ); + } + + #[test] + fn simple_if_else_if_nested_middle() { + check_assist( + unwrap_block, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false { + println!("bar"); + } else if true {<|> + println!("foo"); + } else { + println!("else"); + } + } + "#, + r#" + fn main() { + //bar(); + if true { + println!("true"); + + //comment + //bar(); + } else if false { + println!("bar"); + } + println!("foo"); + } + "#, + ); + } + + #[test] + fn simple_if_bad_cursor_position() { + check_assist_not_applicable( + unwrap_block, + r#" + fn main() { + bar();<|> + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + "#, + ); + } + + #[test] + fn simple_for() { + check_assist( + unwrap_block, + r#" + fn main() { + for i in 0..5 {<|> + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + } + "#, + r#" + fn main() { + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + "#, + ); + } + + #[test] + fn simple_if_in_for() { + check_assist( + unwrap_block, + r#" + fn main() { + for i in 0..5 { + if true {<|> + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + } + "#, + r#" + fn main() { + for i in 0..5 { + foo(); + + //comment + bar(); + } + } + "#, + ); + } + + #[test] + fn simple_loop() { + check_assist( + unwrap_block, + r#" + fn main() { + loop {<|> + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + } + "#, + r#" + fn main() { + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + "#, + ); + } + + #[test] + fn simple_while() { + check_assist( + unwrap_block, + r#" + fn main() { + while true {<|> + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + } + "#, + r#" + fn main() { + if true { + foo(); + + //comment + bar(); + } else { + println!("bar"); + } + } + "#, + ); + } + + #[test] + fn simple_if_in_while_bad_cursor_position() { + check_assist_not_applicable( + unwrap_block, + r#" + fn main() { + while true { + if true { + foo();<|> + + //comment + bar(); + } else { + println!("bar"); + } + } + } + "#, + ); + } +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 64bd87afbd47..464bc03dde9e 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -10,119 +10,113 @@ macro_rules! eprintln { ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; } -mod assist_ctx; -mod marks; +mod assist_config; +mod assist_context; #[cfg(test)] -mod doc_tests; +mod tests; pub mod utils; pub mod ast_transform; -use ra_db::{FileId, FileRange}; -use ra_ide_db::RootDatabase; -use ra_syntax::{TextRange, TextSize}; -use ra_text_edit::TextEdit; - -pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler}; use hir::Semantics; +use ra_db::FileRange; +use ra_ide_db::{source_change::SourceChange, RootDatabase}; +use ra_syntax::TextRange; + +pub(crate) use crate::assist_context::{AssistContext, Assists}; + +pub use assist_config::AssistConfig; /// Unique identifier of the assist, should not be shown to the user /// directly. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AssistId(pub &'static str); -#[derive(Debug, Clone)] -pub struct AssistLabel { - /// Short description of the assist, as shown in the UI. - pub label: String, - pub id: AssistId, -} - #[derive(Clone, Debug)] pub struct GroupLabel(pub String); -impl AssistLabel { - pub(crate) fn new(label: String, id: AssistId) -> AssistLabel { - // FIXME: make fields private, so that this invariant can't be broken - assert!(label.starts_with(|c: char| c.is_uppercase())); - AssistLabel { label, id } - } -} - #[derive(Debug, Clone)] -pub struct AssistAction { - pub edit: TextEdit, - pub cursor_position: Option, - // FIXME: This belongs to `AssistLabel` - pub target: Option, - pub file: AssistFile, +pub struct Assist { + pub id: AssistId, + /// Short description of the assist, as shown in the UI. + pub label: String, + pub group: Option, + /// Target ranges are used to sort assists: the smaller the target range, + /// the more specific assist is, and so it should be sorted first. + pub target: TextRange, } #[derive(Debug, Clone)] pub struct ResolvedAssist { - pub label: AssistLabel, - pub group_label: Option, - pub action: AssistAction, + pub assist: Assist, + pub source_change: SourceChange, } -#[derive(Debug, Clone, Copy)] -pub enum AssistFile { - CurrentFile, - TargetFile(FileId), -} +impl Assist { + /// Return all the assists applicable at the given position. + /// + /// Assists are returned in the "unresolved" state, that is only labels are + /// returned, without actual edits. + pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec { + let sema = Semantics::new(db); + let ctx = AssistContext::new(sema, config, range); + let mut acc = Assists::new_unresolved(&ctx); + handlers::all().iter().for_each(|handler| { + handler(&mut acc, &ctx); + }); + acc.finish_unresolved() + } -impl Default for AssistFile { - fn default() -> Self { - Self::CurrentFile + /// Return all the assists applicable at the given position. + /// + /// Assists are returned in the "resolved" state, that is with edit fully + /// computed. + pub fn resolved( + db: &RootDatabase, + config: &AssistConfig, + range: FileRange, + ) -> Vec { + let sema = Semantics::new(db); + let ctx = AssistContext::new(sema, config, range); + let mut acc = Assists::new_resolved(&ctx); + handlers::all().iter().for_each(|handler| { + handler(&mut acc, &ctx); + }); + acc.finish_resolved() + } + + pub(crate) fn new( + id: AssistId, + label: String, + group: Option, + target: TextRange, + ) -> Assist { + // FIXME: make fields private, so that this invariant can't be broken + assert!(label.starts_with(|c: char| c.is_uppercase())); + Assist { id, label, group, target } } } -/// Return all the assists applicable at the given position. -/// -/// Assists are returned in the "unresolved" state, that is only labels are -/// returned, without actual edits. -pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec { - let sema = Semantics::new(db); - let ctx = AssistCtx::new(&sema, range, false); - handlers::all() - .iter() - .filter_map(|f| f(ctx.clone())) - .flat_map(|it| it.0) - .map(|a| a.label) - .collect() -} - -/// Return all the assists applicable at the given position. -/// -/// Assists are returned in the "resolved" state, that is with edit fully -/// computed. -pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec { - let sema = Semantics::new(db); - let ctx = AssistCtx::new(&sema, range, true); - let mut a = handlers::all() - .iter() - .filter_map(|f| f(ctx.clone())) - .flat_map(|it| it.0) - .map(|it| it.into_resolved().unwrap()) - .collect::>(); - a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len())); - a -} - mod handlers { - use crate::AssistHandler; + use crate::{AssistContext, Assists}; + + pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; mod add_custom_impl; mod add_derive; mod add_explicit_type; + mod add_from_impl_for_enum; mod add_function; mod add_impl; mod add_missing_impl_members; mod add_new; + mod add_turbo_fish; mod apply_demorgan; mod auto_import; + mod change_return_type_to_result; mod change_visibility; mod early_return; mod fill_match_arms; + mod fix_visibility; mod flip_binexpr; mod flip_comma; mod flip_trait_bound; @@ -136,28 +130,32 @@ mod handlers { mod raw_string; mod remove_dbg; mod remove_mut; + mod reorder_fields; mod replace_if_let_with_match; mod replace_let_with_if_let; mod replace_qualified_name_with_use; mod replace_unwrap_with_match; mod split_import; - mod add_from_impl_for_enum; - mod reorder_fields; + mod unwrap_block; - pub(crate) fn all() -> &'static [AssistHandler] { + pub(crate) fn all() -> &'static [Handler] { &[ // These are alphabetic for the foolish consistency add_custom_impl::add_custom_impl, add_derive::add_derive, add_explicit_type::add_explicit_type, + add_from_impl_for_enum::add_from_impl_for_enum, add_function::add_function, add_impl::add_impl, add_new::add_new, + add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, + change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, early_return::convert_to_guarded_return, fill_match_arms::fill_match_arms, + fix_visibility::fix_visibility, flip_binexpr::flip_binexpr, flip_comma::flip_comma, flip_trait_bound::flip_trait_bound, @@ -175,167 +173,18 @@ mod handlers { raw_string::remove_hash, remove_dbg::remove_dbg, remove_mut::remove_mut, + reorder_fields::reorder_fields, replace_if_let_with_match::replace_if_let_with_match, replace_let_with_if_let::replace_let_with_if_let, replace_qualified_name_with_use::replace_qualified_name_with_use, replace_unwrap_with_match::replace_unwrap_with_match, split_import::split_import, - add_from_impl_for_enum::add_from_impl_for_enum, + unwrap_block::unwrap_block, // These are manually sorted for better priorities add_missing_impl_members::add_missing_impl_members, add_missing_impl_members::add_missing_default_members, - reorder_fields::reorder_fields, + // Are you sure you want to add new assist here, and not to the + // sorted list above? ] } } - -#[cfg(test)] -mod helpers { - use std::sync::Arc; - - use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; - use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; - use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; - - use crate::{AssistCtx, AssistFile, AssistHandler}; - use hir::Semantics; - - pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { - let (mut db, file_id) = RootDatabase::with_single_file(text); - // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, - // but it looks like this might need specialization? :( - db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)])); - (db, file_id) - } - - pub(crate) fn check_assist( - assist: AssistHandler, - ra_fixture_before: &str, - ra_fixture_after: &str, - ) { - check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); - } - - // FIXME: instead of having a separate function here, maybe use - // `extract_ranges` and mark the target as ` ` in the - // fixuture? - pub(crate) fn check_assist_target(assist: AssistHandler, ra_fixture: &str, target: &str) { - check(assist, ra_fixture, ExpectedResult::Target(target)); - } - - pub(crate) fn check_assist_not_applicable(assist: AssistHandler, ra_fixture: &str) { - check(assist, ra_fixture, ExpectedResult::NotApplicable); - } - - enum ExpectedResult<'a> { - NotApplicable, - After(&'a str), - Target(&'a str), - } - - fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) { - let (text_without_caret, file_with_caret_id, range_or_offset, db) = - if before.contains("//-") { - let (mut db, position) = RootDatabase::with_position(before); - db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); - ( - db.file_text(position.file_id).as_ref().to_owned(), - position.file_id, - RangeOrOffset::Offset(position.offset), - db, - ) - } else { - let (range_or_offset, text_without_caret) = extract_range_or_offset(before); - let (db, file_id) = with_single_file(&text_without_caret); - (text_without_caret, file_id, range_or_offset, db) - }; - - let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; - - let sema = Semantics::new(&db); - let assist_ctx = AssistCtx::new(&sema, frange, true); - - match (assist(assist_ctx), expected) { - (Some(assist), ExpectedResult::After(after)) => { - let action = assist.0[0].action.clone().unwrap(); - - let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file { - db.file_text(file_id).as_ref().to_owned() - } else { - text_without_caret - }; - - let mut actual = action.edit.apply(&assisted_file_text); - match action.cursor_position { - None => { - if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { - let off = action - .edit - .apply_to_offset(before_cursor_pos) - .expect("cursor position is affected by the edit"); - actual = add_cursor(&actual, off) - } - } - Some(off) => actual = add_cursor(&actual, off), - }; - - assert_eq_text!(after, &actual); - } - (Some(assist), ExpectedResult::Target(target)) => { - let action = assist.0[0].action.clone().unwrap(); - let range = action.target.expect("expected target on action"); - assert_eq_text!(&text_without_caret[range], target); - } - (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), - (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { - panic!("code action is not applicable") - } - (None, ExpectedResult::NotApplicable) => (), - }; - } -} - -#[cfg(test)] -mod tests { - use ra_db::FileRange; - use ra_syntax::TextRange; - use test_utils::{extract_offset, extract_range}; - - use crate::{helpers, resolved_assists}; - - #[test] - fn assist_order_field_struct() { - let before = "struct Foo { <|>bar: u32 }"; - let (before_cursor_pos, before) = extract_offset(before); - let (db, file_id) = helpers::with_single_file(&before); - let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; - let assists = resolved_assists(&db, frange); - let mut assists = assists.iter(); - - assert_eq!( - assists.next().expect("expected assist").label.label, - "Change visibility to pub(crate)" - ); - assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); - } - - #[test] - fn assist_order_if_expr() { - let before = " - pub fn test_some_range(a: int) -> bool { - if let 2..6 = <|>5<|> { - true - } else { - false - } - }"; - let (range, before) = extract_range(before); - let (db, file_id) = helpers::with_single_file(&before); - let frange = FileRange { file_id, range }; - let assists = resolved_assists(&db, frange); - let mut assists = assists.iter(); - - assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); - assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); - } -} diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs deleted file mode 100644 index 8d910205f0ab..000000000000 --- a/crates/ra_assists/src/marks.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! See test_utils/src/marks.rs - -test_utils::marks![ - introduce_var_in_comment_is_not_applicable - test_introduce_var_expr_stmt - test_introduce_var_last_expr - not_applicable_outside_of_bind_pat - test_not_inline_mut_variable - test_not_applicable_if_variable_unused - change_visibility_field_false_positive - test_add_from_impl_already_exists -]; diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs new file mode 100644 index 000000000000..62dd3547fa23 --- /dev/null +++ b/crates/ra_assists/src/tests.rs @@ -0,0 +1,153 @@ +mod generated; + +use std::sync::Arc; + +use hir::Semantics; +use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; +use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; +use ra_syntax::TextRange; +use test_utils::{ + assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset, +}; + +use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; + +pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { + let (mut db, file_id) = RootDatabase::with_single_file(text); + // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, + // but it looks like this might need specialization? :( + db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)])); + (db, file_id) +} + +pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { + check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); +} + +// FIXME: instead of having a separate function here, maybe use +// `extract_ranges` and mark the target as ` ` in the +// fixuture? +pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { + check(assist, ra_fixture, ExpectedResult::Target(target)); +} + +pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { + check(assist, ra_fixture, ExpectedResult::NotApplicable); +} + +fn check_doc_test(assist_id: &str, before: &str, after: &str) { + let (selection, before) = extract_range_or_offset(before); + let (db, file_id) = crate::tests::with_single_file(&before); + let frange = FileRange { file_id, range: selection.into() }; + + let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) + .into_iter() + .find(|assist| assist.assist.id.0 == assist_id) + .unwrap_or_else(|| { + panic!( + "\n\nAssist is not applicable: {}\nAvailable assists: {}", + assist_id, + Assist::resolved(&db, &AssistConfig::default(), frange) + .into_iter() + .map(|assist| assist.assist.id.0) + .collect::>() + .join(", ") + ) + }); + + let actual = { + let change = assist.source_change.source_file_edits.pop().unwrap(); + let mut actual = before.clone(); + change.edit.apply(&mut actual); + actual + }; + assert_eq_text!(after, &actual); +} + +enum ExpectedResult<'a> { + NotApplicable, + After(&'a str), + Target(&'a str), +} + +fn check(handler: Handler, before: &str, expected: ExpectedResult) { + let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { + let (mut db, position) = RootDatabase::with_position(before); + db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); + ( + db.file_text(position.file_id).as_ref().to_owned(), + position.file_id, + RangeOrOffset::Offset(position.offset), + db, + ) + } else { + let (range_or_offset, text_without_caret) = extract_range_or_offset(before); + let (db, file_id) = with_single_file(&text_without_caret); + (text_without_caret, file_id, range_or_offset, db) + }; + + let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; + + let sema = Semantics::new(&db); + let config = AssistConfig::default(); + let ctx = AssistContext::new(sema, &config, frange); + let mut acc = Assists::new_resolved(&ctx); + handler(&mut acc, &ctx); + let mut res = acc.finish_resolved(); + let assist = res.pop(); + match (assist, expected) { + (Some(assist), ExpectedResult::After(after)) => { + let mut source_change = assist.source_change; + let change = source_change.source_file_edits.pop().unwrap(); + + let mut actual = db.file_text(change.file_id).as_ref().to_owned(); + change.edit.apply(&mut actual); + assert_eq_text!(after, &actual); + } + (Some(assist), ExpectedResult::Target(target)) => { + let range = assist.assist.target; + assert_eq_text!(&text_without_caret[range], target); + } + (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), + (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { + panic!("code action is not applicable") + } + (None, ExpectedResult::NotApplicable) => (), + }; +} + +#[test] +fn assist_order_field_struct() { + let before = "struct Foo { <|>bar: u32 }"; + let (before_cursor_pos, before) = extract_offset(before); + let (db, file_id) = with_single_file(&before); + let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; + let assists = Assist::resolved(&db, &AssistConfig::default(), frange); + let mut assists = assists.iter(); + + assert_eq!( + assists.next().expect("expected assist").assist.label, + "Change visibility to pub(crate)" + ); + assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`"); +} + +#[test] +fn assist_order_if_expr() { + let before = " + pub fn test_some_range(a: int) -> bool { + if let 2..6 = <|>5<|> { + true + } else { + false + } + }"; + let (range, before) = extract_range(before); + let (db, file_id) = with_single_file(&before); + let frange = FileRange { file_id, range }; + let assists = Assist::resolved(&db, &AssistConfig::default(), frange); + let mut assists = assists.iter(); + + assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); + assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); +} diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/tests/generated.rs similarity index 82% rename from crates/ra_assists/src/doc_tests/generated.rs rename to crates/ra_assists/src/tests/generated.rs index e4fa9ee366e4..250e56a69624 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -1,10 +1,10 @@ //! Generated file, do not edit by hand, see `xtask/src/codegen` -use super::check; +use super::check_doc_test; #[test] fn doctest_add_custom_impl() { - check( + check_doc_test( "add_custom_impl", r#####" #[derive(Deb<|>ug, Display)] @@ -15,7 +15,7 @@ struct S; struct S; impl Debug for S { - + $0 } "#####, ) @@ -23,7 +23,7 @@ impl Debug for S { #[test] fn doctest_add_derive() { - check( + check_doc_test( "add_derive", r#####" struct Point { @@ -32,7 +32,7 @@ struct Point { } "#####, r#####" -#[derive()] +#[derive($0)] struct Point { x: u32, y: u32, @@ -43,7 +43,7 @@ struct Point { #[test] fn doctest_add_explicit_type() { - check( + check_doc_test( "add_explicit_type", r#####" fn main() { @@ -60,7 +60,7 @@ fn main() { #[test] fn doctest_add_function() { - check( + check_doc_test( "add_function", r#####" struct Baz; @@ -78,7 +78,7 @@ fn foo() { } fn bar(arg: &str, baz: Baz) { - todo!() + ${0:todo!()} } "#####, @@ -87,7 +87,7 @@ fn bar(arg: &str, baz: Baz) { #[test] fn doctest_add_hash() { - check( + check_doc_test( "add_hash", r#####" fn main() { @@ -104,20 +104,20 @@ fn main() { #[test] fn doctest_add_impl() { - check( + check_doc_test( "add_impl", r#####" struct Ctx { - data: T,<|> + data: T,<|> } "#####, r#####" struct Ctx { - data: T, + data: T, } impl Ctx { - + $0 } "#####, ) @@ -125,7 +125,7 @@ impl Ctx { #[test] fn doctest_add_impl_default_members() { - check( + check_doc_test( "add_impl_default_members", r#####" trait Trait { @@ -150,7 +150,7 @@ trait Trait { impl Trait for () { Type X = (); fn foo(&self) {} - fn bar(&self) {} + $0fn bar(&self) {} } "#####, @@ -159,7 +159,7 @@ impl Trait for () { #[test] fn doctest_add_impl_missing_members() { - check( + check_doc_test( "add_impl_missing_members", r#####" trait Trait { @@ -180,7 +180,9 @@ trait Trait { } impl Trait for () { - fn foo(&self) -> u32 { todo!() } + fn foo(&self) -> u32 { + ${0:todo!()} + } } "#####, @@ -189,7 +191,7 @@ impl Trait for () { #[test] fn doctest_add_new() { - check( + check_doc_test( "add_new", r#####" struct Ctx { @@ -202,16 +204,35 @@ struct Ctx { } impl Ctx { - fn new(data: T) -> Self { Self { data } } + fn $0new(data: T) -> Self { Self { data } } } "#####, ) } +#[test] +fn doctest_add_turbo_fish() { + check_doc_test( + "add_turbo_fish", + r#####" +fn make() -> T { todo!() } +fn main() { + let x = make<|>(); +} +"#####, + r#####" +fn make() -> T { todo!() } +fn main() { + let x = make::<${0:_}>(); +} +"#####, + ) +} + #[test] fn doctest_apply_demorgan() { - check( + check_doc_test( "apply_demorgan", r#####" fn main() { @@ -228,7 +249,7 @@ fn main() { #[test] fn doctest_auto_import() { - check( + check_doc_test( "auto_import", r#####" fn main() { @@ -247,9 +268,22 @@ pub mod std { pub mod collections { pub struct HashMap { } } } ) } +#[test] +fn doctest_change_return_type_to_result() { + check_doc_test( + "change_return_type_to_result", + r#####" +fn foo() -> i32<|> { 42i32 } +"#####, + r#####" +fn foo() -> Result { Ok(42i32) } +"#####, + ) +} + #[test] fn doctest_change_visibility() { - check( + check_doc_test( "change_visibility", r#####" <|>fn frobnicate() {} @@ -262,7 +296,7 @@ pub(crate) fn frobnicate() {} #[test] fn doctest_convert_to_guarded_return() { - check( + check_doc_test( "convert_to_guarded_return", r#####" fn main() { @@ -286,7 +320,7 @@ fn main() { #[test] fn doctest_fill_match_arms() { - check( + check_doc_test( "fill_match_arms", r#####" enum Action { Move { distance: u32 }, Stop } @@ -302,7 +336,7 @@ enum Action { Move { distance: u32 }, Stop } fn handle(action: Action) { match action { - Action::Move { distance } => {} + $0Action::Move { distance } => {} Action::Stop => {} } } @@ -310,9 +344,32 @@ fn handle(action: Action) { ) } +#[test] +fn doctest_fix_visibility() { + check_doc_test( + "fix_visibility", + r#####" +mod m { + fn frobnicate() {} +} +fn main() { + m::frobnicate<|>() {} +} +"#####, + r#####" +mod m { + $0pub(crate) fn frobnicate() {} +} +fn main() { + m::frobnicate() {} +} +"#####, + ) +} + #[test] fn doctest_flip_binexpr() { - check( + check_doc_test( "flip_binexpr", r#####" fn main() { @@ -329,7 +386,7 @@ fn main() { #[test] fn doctest_flip_comma() { - check( + check_doc_test( "flip_comma", r#####" fn main() { @@ -346,7 +403,7 @@ fn main() { #[test] fn doctest_flip_trait_bound() { - check( + check_doc_test( "flip_trait_bound", r#####" fn foo Copy>() { } @@ -359,7 +416,7 @@ fn foo() { } #[test] fn doctest_inline_local_variable() { - check( + check_doc_test( "inline_local_variable", r#####" fn main() { @@ -377,7 +434,7 @@ fn main() { #[test] fn doctest_introduce_variable() { - check( + check_doc_test( "introduce_variable", r#####" fn main() { @@ -386,7 +443,7 @@ fn main() { "#####, r#####" fn main() { - let var_name = (1 + 2); + let $0var_name = (1 + 2); var_name * 4; } "#####, @@ -395,7 +452,7 @@ fn main() { #[test] fn doctest_invert_if() { - check( + check_doc_test( "invert_if", r#####" fn main() { @@ -412,7 +469,7 @@ fn main() { #[test] fn doctest_make_raw_string() { - check( + check_doc_test( "make_raw_string", r#####" fn main() { @@ -429,7 +486,7 @@ fn main() { #[test] fn doctest_make_usual_string() { - check( + check_doc_test( "make_usual_string", r#####" fn main() { @@ -446,7 +503,7 @@ fn main() { #[test] fn doctest_merge_imports() { - check( + check_doc_test( "merge_imports", r#####" use std::<|>fmt::Formatter; @@ -460,7 +517,7 @@ use std::{fmt::Formatter, io}; #[test] fn doctest_merge_match_arms() { - check( + check_doc_test( "merge_match_arms", r#####" enum Action { Move { distance: u32 }, Stop } @@ -486,7 +543,7 @@ fn handle(action: Action) { #[test] fn doctest_move_arm_cond_to_match_guard() { - check( + check_doc_test( "move_arm_cond_to_match_guard", r#####" enum Action { Move { distance: u32 }, Stop } @@ -513,7 +570,7 @@ fn handle(action: Action) { #[test] fn doctest_move_bounds_to_where_clause() { - check( + check_doc_test( "move_bounds_to_where_clause", r#####" fn applyF: FnOnce(T) -> U>(f: F, x: T) -> U { @@ -530,7 +587,7 @@ fn apply(f: F, x: T) -> U where F: FnOnce(T) -> U { #[test] fn doctest_move_guard_to_arm_body() { - check( + check_doc_test( "move_guard_to_arm_body", r#####" enum Action { Move { distance: u32 }, Stop } @@ -557,7 +614,7 @@ fn handle(action: Action) { #[test] fn doctest_remove_dbg() { - check( + check_doc_test( "remove_dbg", r#####" fn main() { @@ -574,7 +631,7 @@ fn main() { #[test] fn doctest_remove_hash() { - check( + check_doc_test( "remove_hash", r#####" fn main() { @@ -591,7 +648,7 @@ fn main() { #[test] fn doctest_remove_mut() { - check( + check_doc_test( "remove_mut", r#####" impl Walrus { @@ -608,7 +665,7 @@ impl Walrus { #[test] fn doctest_reorder_fields() { - check( + check_doc_test( "reorder_fields", r#####" struct Foo {foo: i32, bar: i32}; @@ -623,7 +680,7 @@ const test: Foo = Foo {foo: 1, bar: 0} #[test] fn doctest_replace_if_let_with_match() { - check( + check_doc_test( "replace_if_let_with_match", r#####" enum Action { Move { distance: u32 }, Stop } @@ -651,7 +708,7 @@ fn handle(action: Action) { #[test] fn doctest_replace_let_with_if_let() { - check( + check_doc_test( "replace_let_with_if_let", r#####" enum Option { Some(T), None } @@ -677,7 +734,7 @@ fn compute() -> Option { None } #[test] fn doctest_replace_qualified_name_with_use() { - check( + check_doc_test( "replace_qualified_name_with_use", r#####" fn process(map: std::collections::<|>HashMap) {} @@ -692,7 +749,7 @@ fn process(map: HashMap) {} #[test] fn doctest_replace_unwrap_with_match() { - check( + check_doc_test( "replace_unwrap_with_match", r#####" enum Result { Ok(T), Err(E) } @@ -707,7 +764,7 @@ fn main() { let x: Result = Result::Ok(92); let y = match x { Ok(a) => a, - _ => unreachable!(), + $0_ => unreachable!(), }; } "#####, @@ -716,7 +773,7 @@ fn main() { #[test] fn doctest_split_import() { - check( + check_doc_test( "split_import", r#####" use std::<|>collections::HashMap; @@ -726,3 +783,22 @@ use std::{collections::HashMap}; "#####, ) } + +#[test] +fn doctest_unwrap_block() { + check_doc_test( + "unwrap_block", + r#####" +fn foo() { + if true {<|> + println!("foo"); + } +} +"#####, + r#####" +fn foo() { + println!("foo"); +} +"#####, + ) +} diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index efd9886978a5..0038a9764b15 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs @@ -1,19 +1,58 @@ //! Assorted functions shared by several assists. pub(crate) mod insert_use; -use std::iter; +use std::{iter, ops}; -use hir::{Adt, Crate, Semantics, Trait, Type}; +use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type}; use ra_ide_db::RootDatabase; use ra_syntax::{ ast::{self, make, NameOwner}, - AstNode, T, + AstNode, SyntaxNode, T, }; use rustc_hash::FxHashSet; -pub use insert_use::insert_use_statement; +use crate::assist_config::SnippetCap; -pub fn get_missing_impl_items( +pub(crate) use insert_use::insert_use_statement; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum Cursor<'a> { + Replace(&'a SyntaxNode), + Before(&'a SyntaxNode), +} + +impl<'a> Cursor<'a> { + fn node(self) -> &'a SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} + +pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String { + assert!(cursor.node().ancestors().any(|it| it == *node)); + let range = cursor.node().text_range() - node.text_range().start(); + let range: ops::Range = range.into(); + + let mut placeholder = cursor.node().to_string(); + escape(&mut placeholder); + let tab_stop = match cursor { + Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder), + Cursor::Before(placeholder) => format!("$0{}", placeholder), + }; + + let mut buf = node.to_string(); + buf.replace_range(range, &tab_stop); + return buf; + + fn escape(buf: &mut String) { + stdx::replace(buf, '{', r"\{"); + stdx::replace(buf, '}', r"\}"); + stdx::replace(buf, '$', r"\$"); + } +} + +pub fn get_missing_assoc_items( sema: &Semantics, impl_def: &ast::ImplDef, ) -> Vec { @@ -23,21 +62,21 @@ pub fn get_missing_impl_items( let mut impl_type = FxHashSet::default(); if let Some(item_list) = impl_def.item_list() { - for item in item_list.impl_items() { + for item in item_list.assoc_items() { match item { - ast::ImplItem::FnDef(f) => { + ast::AssocItem::FnDef(f) => { if let Some(n) = f.name() { impl_fns_consts.insert(n.syntax().to_string()); } } - ast::ImplItem::TypeAliasDef(t) => { + ast::AssocItem::TypeAliasDef(t) => { if let Some(n) = t.name() { impl_type.insert(n.syntax().to_string()); } } - ast::ImplItem::ConstDef(c) => { + ast::AssocItem::ConstDef(c) => { if let Some(n) = c.name() { impl_fns_consts.insert(n.syntax().to_string()); } @@ -103,7 +142,7 @@ fn invert_special_case(expr: &ast::Expr) -> Option { } #[derive(Clone, Copy)] -pub(crate) enum TryEnum { +pub enum TryEnum { Result, Option, } @@ -111,7 +150,7 @@ pub(crate) enum TryEnum { impl TryEnum { const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; - pub(crate) fn from_ty(sema: &Semantics, ty: &Type) -> Option { + pub fn from_ty(sema: &Semantics, ty: &Type) -> Option { let enum_ = match ty.as_adt() { Some(Adt::Enum(it)) => it, _ => return None, @@ -161,13 +200,19 @@ impl FamousDefs<'_, '_> { #[cfg(test)] pub(crate) const FIXTURE: &'static str = r#" //- /libcore.rs crate:core -pub mod convert{ +pub mod convert { pub trait From { fn from(T) -> Self; } } -pub mod prelude { pub use crate::convert::From } +pub mod option { + pub enum Option { None, Some(T)} +} + +pub mod prelude { + pub use crate::{convert::From, option::Option::{self, *}}; +} #[prelude_import] pub use prelude::*; "#; @@ -176,7 +221,25 @@ pub use prelude::*; self.find_trait("core:convert:From") } + pub(crate) fn core_option_Option(&self) -> Option { + self.find_enum("core:option:Option") + } + fn find_trait(&self, path: &str) -> Option { + match self.find_def(path)? { + hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), + _ => None, + } + } + + fn find_enum(&self, path: &str) -> Option { + match self.find_def(path)? { + hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it), + _ => None, + } + } + + fn find_def(&self, path: &str) -> Option { let db = self.0.db; let mut path = path.split(':'); let trait_ = path.next_back()?; @@ -201,9 +264,6 @@ pub use prelude::*; } let def = module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; - match def { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), - _ => None, - } + Some(def) } } diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs index c507e71e08fa..0ee43482f798 100644 --- a/crates/ra_assists/src/utils/insert_use.rs +++ b/crates/ra_assists/src/utils/insert_use.rs @@ -11,17 +11,20 @@ use ra_syntax::{ }; use ra_text_edit::TextEditBuilder; +use crate::assist_context::AssistContext; + /// Creates and inserts a use statement for the given path to import. /// The use statement is inserted in the scope most appropriate to the /// the cursor position given, additionally merged with the existing use imports. -pub fn insert_use_statement( +pub(crate) fn insert_use_statement( // Ideally the position of the cursor, used to position: &SyntaxNode, path_to_import: &ModPath, - edit: &mut TextEditBuilder, + ctx: &AssistContext, + builder: &mut TextEditBuilder, ) { let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::>(); - let container = position.ancestors().find_map(|n| { + let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { if let Some(module) = ast::Module::cast(n.clone()) { return module.item_list().map(|it| it.syntax().clone()); } @@ -30,7 +33,7 @@ pub fn insert_use_statement( if let Some(container) = container { let action = best_action_for_target(container, position.clone(), &target); - make_assist(&action, &target, edit); + make_assist(&action, &target, builder); } } diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs index 51d953f6e896..57feabcb27cb 100644 --- a/crates/ra_cfg/src/lib.rs +++ b/crates/ra_cfg/src/lib.rs @@ -2,8 +2,6 @@ mod cfg_expr; -use std::iter::IntoIterator; - use ra_syntax::SmolStr; use rustc_hash::FxHashSet; @@ -48,9 +46,4 @@ impl CfgOptions { pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { self.key_values.insert((key, value)); } - - /// Shortcut to set features - pub fn insert_features(&mut self, iter: impl IntoIterator) { - iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat)); - } } diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index 8248684eeac1..f8f7670919c2 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs @@ -1,4 +1,62 @@ -//! FIXME: write short doc here +//! Fixtures are strings containing rust source code with optional metadata. +//! A fixture without metadata is parsed into a single source file. +//! Use this to test functionality local to one file. +//! +//! Simple Example: +//! ``` +//! r#" +//! fn main() { +//! println!("Hello World") +//! } +//! "# +//! ``` +//! +//! Metadata can be added to a fixture after a `//-` comment. +//! The basic form is specifying filenames, +//! which is also how to define multiple files in a single test fixture +//! +//! Example using two files in the same crate: +//! ``` +//! " +//! //- /main.rs +//! mod foo; +//! fn main() { +//! foo::bar(); +//! } +//! +//! //- /foo.rs +//! pub fn bar() {} +//! " +//! ``` +//! +//! Example using two crates with one file each, with one crate depending on the other: +//! ``` +//! r#" +//! //- /main.rs crate:a deps:b +//! fn main() { +//! b::foo(); +//! } +//! //- /lib.rs crate:b +//! pub fn b() { +//! println!("Hello World") +//! } +//! "# +//! ``` +//! +//! Metadata allows specifying all settings and variables +//! that are available in a real rust project: +//! - crate names via `crate:cratename` +//! - dependencies via `deps:dep1,dep2` +//! - configuration settings via `cfg:dbg=false,opt_level=2` +//! - environment variables via `env:PATH=/bin,RUST_LOG=debug` +//! +//! Example using all available metadata: +//! ``` +//! " +//! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo +//! fn insert_source_code_here() {} +//! " +//! ``` use std::str::FromStr; use std::sync::Arc; diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml index 324c33d9dd28..1aa39badea96 100644 --- a/crates/ra_flycheck/Cargo.toml +++ b/crates/ra_flycheck/Cargo.toml @@ -4,13 +4,13 @@ name = "ra_flycheck" version = "0.1.0" authors = ["rust-analyzer developers"] +[lib] +doctest = false + [dependencies] crossbeam-channel = "0.4.0" -lsp-types = { version = "0.74.0", features = ["proposed"] } log = "0.4.8" -cargo_metadata = "0.9.1" +cargo_metadata = "0.10.0" serde_json = "1.0.48" jod-thread = "0.1.1" - -[dev-dependencies] -insta = "0.16.0" +ra_toolchain = { path = "../ra_toolchain" } diff --git a/crates/ra_flycheck/src/conv.rs b/crates/ra_flycheck/src/conv.rs deleted file mode 100644 index 817543deb197..000000000000 --- a/crates/ra_flycheck/src/conv.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! This module provides the functionality needed to convert diagnostics from -//! `cargo check` json format to the LSP diagnostic format. -use cargo_metadata::diagnostic::{ - Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, - DiagnosticSpanMacroExpansion, -}; -use lsp_types::{ - CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, - Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, -}; -use std::{ - collections::HashMap, - fmt::Write, - path::{Component, Path, PathBuf, Prefix}, - str::FromStr, -}; - -#[cfg(test)] -mod test; - -/// Converts a Rust level string to a LSP severity -fn map_level_to_severity(val: DiagnosticLevel) -> Option { - match val { - DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), - DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), - DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), - DiagnosticLevel::Unknown => None, - } -} - -/// Check whether a file name is from macro invocation -fn is_from_macro(file_name: &str) -> bool { - file_name.starts_with('<') && file_name.ends_with('>') -} - -/// Converts a Rust macro span to a LSP location recursively -fn map_macro_span_to_location( - span_macro: &DiagnosticSpanMacroExpansion, - workspace_root: &PathBuf, -) -> Option { - if !is_from_macro(&span_macro.span.file_name) { - return Some(map_span_to_location(&span_macro.span, workspace_root)); - } - - if let Some(expansion) = &span_macro.span.expansion { - return map_macro_span_to_location(&expansion, workspace_root); - } - - None -} - -/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary -fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { - if span.expansion.is_some() { - let expansion = span.expansion.as_ref().unwrap(); - if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { - return macro_range; - } - } - - map_span_to_location_naive(span, workspace_root) -} - -/// Converts a Rust span to a LSP location -fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { - let mut file_name = workspace_root.clone(); - file_name.push(&span.file_name); - let uri = url_from_path_with_drive_lowercasing(file_name).unwrap(); - - let range = Range::new( - Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), - Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), - ); - - Location { uri, range } -} - -/// Converts a secondary Rust span to a LSP related information -/// -/// If the span is unlabelled this will return `None`. -fn map_secondary_span_to_related( - span: &DiagnosticSpan, - workspace_root: &PathBuf, -) -> Option { - if let Some(label) = &span.label { - let location = map_span_to_location(span, workspace_root); - Some(DiagnosticRelatedInformation { location, message: label.clone() }) - } else { - // Nothing to label this with - None - } -} - -/// Determines if diagnostic is related to unused code -fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" - | "unused_imports" | "unused_macros" | "unused_variables" => true, - _ => false, - } - } else { - false - } -} - -/// Determines if diagnostic is related to deprecated code -fn is_deprecated(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "deprecated" => true, - _ => false, - } - } else { - false - } -} - -enum MappedRustChildDiagnostic { - Related(DiagnosticRelatedInformation), - SuggestedFix(CodeAction), - MessageLine(String), -} - -fn map_rust_child_diagnostic( - rd: &RustDiagnostic, - workspace_root: &PathBuf, -) -> MappedRustChildDiagnostic { - let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); - if spans.is_empty() { - // `rustc` uses these spanless children as a way to print multi-line - // messages - return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); - } - - let mut edit_map: HashMap> = HashMap::new(); - for &span in &spans { - match (&span.suggestion_applicability, &span.suggested_replacement) { - (Some(Applicability::MachineApplicable), Some(suggested_replacement)) => { - let location = map_span_to_location(span, workspace_root); - let edit = TextEdit::new(location.range, suggested_replacement.clone()); - edit_map.entry(location.uri).or_default().push(edit); - } - _ => {} - } - } - - if !edit_map.is_empty() { - MappedRustChildDiagnostic::SuggestedFix(CodeAction { - title: rd.message.clone(), - kind: Some("quickfix".to_string()), - diagnostics: None, - edit: Some(WorkspaceEdit::new(edit_map)), - command: None, - is_preferred: None, - }) - } else { - MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { - location: map_span_to_location(spans[0], workspace_root), - message: rd.message.clone(), - }) - } -} - -#[derive(Debug)] -pub(crate) struct MappedRustDiagnostic { - pub location: Location, - pub diagnostic: Diagnostic, - pub fixes: Vec, -} - -/// Converts a Rust root diagnostic to LSP form -/// -/// This flattens the Rust diagnostic by: -/// -/// 1. Creating a LSP diagnostic with the root message and primary span. -/// 2. Adding any labelled secondary spans to `relatedInformation` -/// 3. Categorising child diagnostics as either `SuggestedFix`es, -/// `relatedInformation` or additional message lines. -/// -/// If the diagnostic has no primary span this will return `None` -pub(crate) fn map_rust_diagnostic_to_lsp( - rd: &RustDiagnostic, - workspace_root: &PathBuf, -) -> Vec { - let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); - if primary_spans.is_empty() { - return vec![]; - } - - let severity = map_level_to_severity(rd.level); - - let mut source = String::from("rustc"); - let mut code = rd.code.as_ref().map(|c| c.code.clone()); - if let Some(code_val) = &code { - // See if this is an RFC #2103 scoped lint (e.g. from Clippy) - let scoped_code: Vec<&str> = code_val.split("::").collect(); - if scoped_code.len() == 2 { - source = String::from(scoped_code[0]); - code = Some(String::from(scoped_code[1])); - } - } - - let mut needs_primary_span_label = true; - let mut related_information = vec![]; - let mut tags = vec![]; - - for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { - let related = map_secondary_span_to_related(secondary_span, workspace_root); - if let Some(related) = related { - related_information.push(related); - } - } - - let mut fixes = vec![]; - let mut message = rd.message.clone(); - for child in &rd.children { - let child = map_rust_child_diagnostic(&child, workspace_root); - match child { - MappedRustChildDiagnostic::Related(related) => related_information.push(related), - MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), - MappedRustChildDiagnostic::MessageLine(message_line) => { - write!(&mut message, "\n{}", message_line).unwrap(); - - // These secondary messages usually duplicate the content of the - // primary span label. - needs_primary_span_label = false; - } - } - } - - if is_unused_or_unnecessary(rd) { - tags.push(DiagnosticTag::Unnecessary); - } - - if is_deprecated(rd) { - tags.push(DiagnosticTag::Deprecated); - } - - primary_spans - .iter() - .map(|primary_span| { - let location = map_span_to_location(&primary_span, workspace_root); - - let mut message = message.clone(); - if needs_primary_span_label { - if let Some(primary_span_label) = &primary_span.label { - write!(&mut message, "\n{}", primary_span_label).unwrap(); - } - } - - // If error occurs from macro expansion, add related info pointing to - // where the error originated - if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { - let def_loc = map_span_to_location_naive(&primary_span, workspace_root); - related_information.push(DiagnosticRelatedInformation { - location: def_loc, - message: "Error originated from macro here".to_string(), - }); - } - - let diagnostic = Diagnostic { - range: location.range, - severity, - code: code.clone().map(NumberOrString::String), - source: Some(source.clone()), - message, - related_information: if !related_information.is_empty() { - Some(related_information.clone()) - } else { - None - }, - tags: if !tags.is_empty() { Some(tags.clone()) } else { None }, - }; - - MappedRustDiagnostic { location, diagnostic, fixes: fixes.clone() } - }) - .collect() -} - -/// Returns a `Url` object from a given path, will lowercase drive letters if present. -/// This will only happen when processing windows paths. -/// -/// When processing non-windows path, this is essentially the same as `Url::from_file_path`. -pub fn url_from_path_with_drive_lowercasing( - path: impl AsRef, -) -> Result> { - let component_has_windows_drive = path.as_ref().components().any(|comp| { - if let Component::Prefix(c) = comp { - match c.kind() { - Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true, - _ => return false, - } - } - false - }); - - // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters. - if component_has_windows_drive { - let url_original = Url::from_file_path(&path) - .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?; - - let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect(); - - // There is a drive partition, but we never found a colon. - // This should not happen, but in this case we just pass it through. - if drive_partition.len() == 1 { - return Ok(url_original); - } - - let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0]; - let url = Url::from_str(&joined).expect("This came from a valid `Url`"); - - Ok(url) - } else { - Ok(Url::from_file_path(&path) - .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?) - } -} - -// `Url` is not able to parse windows paths on unix machines. -#[cfg(target_os = "windows")] -#[cfg(test)] -mod path_conversion_windows_tests { - use super::url_from_path_with_drive_lowercasing; - #[test] - fn test_lowercase_drive_letter_with_drive() { - let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap(); - - assert_eq!(url.to_string(), "file:///c:/Test"); - } - - #[test] - fn test_drive_without_colon_passthrough() { - let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap(); - - assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); - } -} diff --git a/crates/ra_flycheck/src/conv/test.rs b/crates/ra_flycheck/src/conv/test.rs deleted file mode 100644 index 4e81455ca177..000000000000 --- a/crates/ra_flycheck/src/conv/test.rs +++ /dev/null @@ -1,1072 +0,0 @@ -//! This module contains the large and verbose snapshot tests for the -//! conversions between `cargo check` json and LSP diagnostics. -#[cfg(not(windows))] -use crate::*; - -#[cfg(not(windows))] -fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic { - serde_json::from_str::(val).unwrap() -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_incompatible_type_for_trait() { - let diag = parse_diagnostic( - r##"{ - "message": "method `next` has an incompatible type for trait", - "code": { - "code": "E0053", - "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n" - }, - "level": "error", - "spans": [ - { - "file_name": "compiler/ty/list_iter.rs", - "byte_start": 1307, - "byte_end": 1350, - "line_start": 52, - "line_end": 52, - "column_start": 5, - "column_end": 48, - "is_primary": true, - "text": [ - { - "text": " fn next(&self) -> Option<&'list ty::Ref> {", - "highlight_start": 5, - "highlight_end": 48 - } - ], - "label": "types differ in mutability", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [ - { - "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - } - ], - "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`\n\n" - } - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_unused_variable() { - let diag = parse_diagnostic( - r##"{ -"message": "unused variable: `foo`", -"code": { - "code": "unused_variables", - "explanation": null -}, -"level": "warning", -"spans": [ - { - "file_name": "driver/subcommand/repl.rs", - "byte_start": 9228, - "byte_end": 9231, - "line_start": 291, - "line_end": 291, - "column_start": 9, - "column_end": 12, - "is_primary": true, - "text": [ - { - "text": " let foo = 42;", - "highlight_start": 9, - "highlight_end": 12 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [ - { - "message": "#[warn(unused_variables)] on by default", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "consider prefixing with an underscore", - "code": null, - "level": "help", - "spans": [ - { - "file_name": "driver/subcommand/repl.rs", - "byte_start": 9228, - "byte_end": 9231, - "line_start": 291, - "line_end": 291, - "column_start": 9, - "column_end": 12, - "is_primary": true, - "text": [ - { - "text": " let foo = 42;", - "highlight_start": 9, - "highlight_end": 12 - } - ], - "label": null, - "suggested_replacement": "_foo", - "suggestion_applicability": "MachineApplicable", - "expansion": null - } - ], - "children": [], - "rendered": null - } -], -"rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_wrong_number_of_parameters() { - let diag = parse_diagnostic( - r##"{ -"message": "this function takes 2 parameters but 3 parameters were supplied", -"code": { - "code": "E0061", - "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n" -}, -"level": "error", -"spans": [ - { - "file_name": "compiler/ty/select.rs", - "byte_start": 8787, - "byte_end": 9241, - "line_start": 219, - "line_end": 231, - "column_start": 5, - "column_end": 6, - "is_primary": false, - "text": [ - { - "text": " pub fn add_evidence(", - "highlight_start": 5, - "highlight_end": 25 - }, - { - "text": " &mut self,", - "highlight_start": 1, - "highlight_end": 19 - }, - { - "text": " target_poly: &ty::Ref,", - "highlight_start": 1, - "highlight_end": 41 - }, - { - "text": " evidence_poly: &ty::Ref,", - "highlight_start": 1, - "highlight_end": 43 - }, - { - "text": " ) {", - "highlight_start": 1, - "highlight_end": 8 - }, - { - "text": " match target_poly {", - "highlight_start": 1, - "highlight_end": 28 - }, - { - "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),", - "highlight_start": 1, - "highlight_end": 81 - }, - { - "text": " ty::Ref::Fixed(target_ty) => {", - "highlight_start": 1, - "highlight_end": 43 - }, - { - "text": " let evidence_ty = evidence_poly.resolve_to_ty();", - "highlight_start": 1, - "highlight_end": 65 - }, - { - "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)", - "highlight_start": 1, - "highlight_end": 76 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 14 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 10 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 6 - } - ], - "label": "defined here", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - }, - { - "file_name": "compiler/ty/select.rs", - "byte_start": 4045, - "byte_end": 4057, - "line_start": 104, - "line_end": 104, - "column_start": 18, - "column_end": 30, - "is_primary": true, - "text": [ - { - "text": " self.add_evidence(target_fixed, evidence_fixed, false);", - "highlight_start": 18, - "highlight_end": 30 - } - ], - "label": "expected 2 parameters", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [], -"rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref,\n222 | | evidence_poly: &ty::Ref,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_clippy_pass_by_ref() { - let diag = parse_diagnostic( - r##"{ -"message": "this argument is passed by reference, but would be more efficient if passed by value", -"code": { - "code": "clippy::trivially_copy_pass_by_ref", - "explanation": null -}, -"level": "warning", -"spans": [ - { - "file_name": "compiler/mir/tagset.rs", - "byte_start": 941, - "byte_end": 946, - "line_start": 42, - "line_end": 42, - "column_start": 24, - "column_end": 29, - "is_primary": true, - "text": [ - { - "text": " pub fn is_disjoint(&self, other: Self) -> bool {", - "highlight_start": 24, - "highlight_end": 29 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [ - { - "message": "lint level defined here", - "code": null, - "level": "note", - "spans": [ - { - "file_name": "compiler/lib.rs", - "byte_start": 8, - "byte_end": 19, - "line_start": 1, - "line_end": 1, - "column_start": 9, - "column_end": 20, - "is_primary": true, - "text": [ - { - "text": "#![warn(clippy::all)]", - "highlight_start": 9, - "highlight_end": 20 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [], - "rendered": null - }, - { - "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", - "code": null, - "level": "help", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "consider passing by value instead", - "code": null, - "level": "help", - "spans": [ - { - "file_name": "compiler/mir/tagset.rs", - "byte_start": 941, - "byte_end": 946, - "line_start": 42, - "line_end": 42, - "column_start": 24, - "column_end": 29, - "is_primary": true, - "text": [ - { - "text": " pub fn is_disjoint(&self, other: Self) -> bool {", - "highlight_start": 24, - "highlight_end": 29 - } - ], - "label": null, - "suggested_replacement": "self", - "suggestion_applicability": "Unspecified", - "expansion": null - } - ], - "children": [], - "rendered": null - } -], -"rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_mismatched_type() { - let diag = parse_diagnostic( - r##"{ -"message": "mismatched types", -"code": { - "code": "E0308", - "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" -}, -"level": "error", -"spans": [ - { - "file_name": "runtime/compiler_support.rs", - "byte_start": 1589, - "byte_end": 1594, - "line_start": 48, - "line_end": 48, - "column_start": 65, - "column_end": 70, - "is_primary": true, - "text": [ - { - "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);", - "highlight_start": 65, - "highlight_end": 70 - } - ], - "label": "expected usize, found u32", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [], -"rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_handles_macro_location() { - let diag = parse_diagnostic( - r##"{ -"rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n", -"children": [ - { - "children": [], - "code": null, - "level": "help", - "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", - "rendered": null, - "spans": [] - } -], -"code": { - "code": "E0277", - "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n" -}, -"level": "error", -"message": "can't compare `{integer}` with `&str`", -"spans": [ - { - "byte_end": 155, - "byte_start": 153, - "column_end": 33, - "column_start": 31, - "expansion": { - "def_site_span": { - "byte_end": 940, - "byte_start": 0, - "column_end": 6, - "column_start": 1, - "expansion": null, - "file_name": "<::core::macros::assert_eq macros>", - "is_primary": false, - "label": null, - "line_end": 36, - "line_start": 1, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 35, - "highlight_start": 1, - "text": "($ left : expr, $ right : expr) =>" - }, - { - "highlight_end": 3, - "highlight_start": 1, - "text": "({" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " match (& $ left, & $ right)" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 34, - "highlight_start": 1, - "text": " (left_val, right_val) =>" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 46, - "highlight_start": 1, - "text": " if ! (* left_val == * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 25, - "highlight_start": 1, - "text": " panic !" - }, - { - "highlight_end": 57, - "highlight_start": 1, - "text": " (r#\"assertion failed: `(left == right)`" - }, - { - "highlight_end": 16, - "highlight_start": 1, - "text": " left: `{:?}`," - }, - { - "highlight_end": 18, - "highlight_start": 1, - "text": " right: `{:?}`\"#," - }, - { - "highlight_end": 47, - "highlight_start": 1, - "text": " & * left_val, & * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 42, - "highlight_start": 1, - "text": " }) ; ($ left : expr, $ right : expr,) =>" - }, - { - "highlight_end": 49, - "highlight_start": 1, - "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;" - }, - { - "highlight_end": 53, - "highlight_start": 1, - "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>" - }, - { - "highlight_end": 3, - "highlight_start": 1, - "text": "({" - }, - { - "highlight_end": 37, - "highlight_start": 1, - "text": " match (& ($ left), & ($ right))" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 34, - "highlight_start": 1, - "text": " (left_val, right_val) =>" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 46, - "highlight_start": 1, - "text": " if ! (* left_val == * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 25, - "highlight_start": 1, - "text": " panic !" - }, - { - "highlight_end": 57, - "highlight_start": 1, - "text": " (r#\"assertion failed: `(left == right)`" - }, - { - "highlight_end": 16, - "highlight_start": 1, - "text": " left: `{:?}`," - }, - { - "highlight_end": 22, - "highlight_start": 1, - "text": " right: `{:?}`: {}\"#," - }, - { - "highlight_end": 72, - "highlight_start": 1, - "text": " & * left_val, & * right_val, $ crate :: format_args !" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " ($ ($ arg) +))" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 6, - "highlight_start": 1, - "text": " }) ;" - } - ] - }, - "macro_decl_name": "assert_eq!", - "span": { - "byte_end": 38, - "byte_start": 16, - "column_end": 27, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": false, - "label": null, - "line_end": 2, - "line_start": 2, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 27, - "highlight_start": 5, - "text": " assert_eq!(1, \"love\");" - } - ] - } - }, - "file_name": "<::core::macros::assert_eq macros>", - "is_primary": true, - "label": "no implementation for `{integer} == &str`", - "line_end": 7, - "line_start": 7, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 33, - "highlight_start": 31, - "text": " if ! (* left_val == * right_val)" - } - ] - } -] -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_macro_compiler_error() { - let diag = parse_diagnostic( - r##"{ - "rendered": "error: Please register your known path in the path module\n --> crates/ra_hir_def/src/path.rs:265:9\n |\n265 | compile_error!(\"Please register your known path in the path module\")\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n | \n ::: crates/ra_hir_def/src/data.rs:80:16\n |\n80 | let path = path![std::future::Future];\n | -------------------------- in this macro invocation\n\n", - "children": [], - "code": null, - "level": "error", - "message": "Please register your known path in the path module", - "spans": [ - { - "byte_end": 8285, - "byte_start": 8217, - "column_end": 77, - "column_start": 9, - "expansion": { - "def_site_span": { - "byte_end": 8294, - "byte_start": 7858, - "column_end": 2, - "column_start": 1, - "expansion": null, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": false, - "label": null, - "line_end": 267, - "line_start": 254, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 28, - "highlight_start": 1, - "text": "macro_rules! __known_path {" - }, - { - "highlight_end": 37, - "highlight_start": 1, - "text": " (std::iter::IntoIterator) => {};" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " (std::result::Result) => {};" - }, - { - "highlight_end": 29, - "highlight_start": 1, - "text": " (std::ops::Range) => {};" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " (std::ops::RangeFrom) => {};" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " (std::ops::RangeFull) => {};" - }, - { - "highlight_end": 31, - "highlight_start": 1, - "text": " (std::ops::RangeTo) => {};" - }, - { - "highlight_end": 40, - "highlight_start": 1, - "text": " (std::ops::RangeToInclusive) => {};" - }, - { - "highlight_end": 38, - "highlight_start": 1, - "text": " (std::ops::RangeInclusive) => {};" - }, - { - "highlight_end": 27, - "highlight_start": 1, - "text": " (std::ops::Try) => {};" - }, - { - "highlight_end": 22, - "highlight_start": 1, - "text": " ($path:path) => {" - }, - { - "highlight_end": 77, - "highlight_start": 1, - "text": " compile_error!(\"Please register your known path in the path module\")" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " };" - }, - { - "highlight_end": 2, - "highlight_start": 1, - "text": "}" - } - ] - }, - "macro_decl_name": "$crate::__known_path!", - "span": { - "byte_end": 8427, - "byte_start": 8385, - "column_end": 51, - "column_start": 9, - "expansion": { - "def_site_span": { - "byte_end": 8611, - "byte_start": 8312, - "column_end": 2, - "column_start": 1, - "expansion": null, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": false, - "label": null, - "line_end": 277, - "line_start": 270, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 22, - "highlight_start": 1, - "text": "macro_rules! __path {" - }, - { - "highlight_end": 43, - "highlight_start": 1, - "text": " ($start:ident $(:: $seg:ident)*) => ({" - }, - { - "highlight_end": 51, - "highlight_start": 1, - "text": " $crate::__known_path!($start $(:: $seg)*);" - }, - { - "highlight_end": 87, - "highlight_start": 1, - "text": " $crate::path::ModPath::from_simple_segments($crate::path::PathKind::Abs, vec![" - }, - { - "highlight_end": 76, - "highlight_start": 1, - "text": " $crate::path::__name![$start], $($crate::path::__name![$seg],)*" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " ])" - }, - { - "highlight_end": 8, - "highlight_start": 1, - "text": " });" - }, - { - "highlight_end": 2, - "highlight_start": 1, - "text": "}" - } - ] - }, - "macro_decl_name": "path!", - "span": { - "byte_end": 2966, - "byte_start": 2940, - "column_end": 42, - "column_start": 16, - "expansion": null, - "file_name": "crates/ra_hir_def/src/data.rs", - "is_primary": false, - "label": null, - "line_end": 80, - "line_start": 80, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 42, - "highlight_start": 16, - "text": " let path = path![std::future::Future];" - } - ] - } - }, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": false, - "label": null, - "line_end": 272, - "line_start": 272, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 51, - "highlight_start": 9, - "text": " $crate::__known_path!($start $(:: $seg)*);" - } - ] - } - }, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": true, - "label": null, - "line_end": 265, - "line_start": 265, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 77, - "highlight_start": 9, - "text": " compile_error!(\"Please register your known path in the path module\")" - } - ] - } - ] -} - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_multi_line_fix() { - let diag = parse_diagnostic( - r##"{ - "rendered": "warning: returning the result of a let binding from a block\n --> src/main.rs:4:5\n |\n3 | let a = (0..10).collect();\n | -------------------------- unnecessary let binding\n4 | a\n | ^\n |\n = note: `#[warn(clippy::let_and_return)]` on by default\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return\nhelp: return the expression directly\n |\n3 | \n4 | (0..10).collect()\n |\n\n", - "children": [ - { - "children": [], - "code": null, - "level": "note", - "message": "`#[warn(clippy::let_and_return)]` on by default", - "rendered": null, - "spans": [] - }, - { - "children": [], - "code": null, - "level": "help", - "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return", - "rendered": null, - "spans": [] - }, - { - "children": [], - "code": null, - "level": "help", - "message": "return the expression directly", - "rendered": null, - "spans": [ - { - "byte_end": 55, - "byte_start": 29, - "column_end": 31, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": true, - "label": null, - "line_end": 3, - "line_start": 3, - "suggested_replacement": "", - "suggestion_applicability": "MachineApplicable", - "text": [ - { - "highlight_end": 31, - "highlight_start": 5, - "text": " let a = (0..10).collect();" - } - ] - }, - { - "byte_end": 61, - "byte_start": 60, - "column_end": 6, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": true, - "label": null, - "line_end": 4, - "line_start": 4, - "suggested_replacement": "(0..10).collect()", - "suggestion_applicability": "MachineApplicable", - "text": [ - { - "highlight_end": 6, - "highlight_start": 5, - "text": " a" - } - ] - } - ] - } - ], - "code": { - "code": "clippy::let_and_return", - "explanation": null - }, - "level": "warning", - "message": "returning the result of a let binding from a block", - "spans": [ - { - "byte_end": 55, - "byte_start": 29, - "column_end": 31, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": false, - "label": "unnecessary let binding", - "line_end": 3, - "line_start": 3, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 31, - "highlight_start": 5, - "text": " let a = (0..10).collect();" - } - ] - }, - { - "byte_end": 61, - "byte_start": 60, - "column_end": 6, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": true, - "label": null, - "line_end": 4, - "line_start": 4, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 6, - "highlight_start": 5, - "text": " a" - } - ] - } - ] - } - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index b54a30ab85f8..041e38a9ff4c 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -1,11 +1,9 @@ //! cargo_check provides the functionality needed to run `cargo check` or //! another compatible command (f.x. clippy) in a background thread and provide //! LSP diagnostics based on the output of the command. -mod conv; use std::{ - env, - io::{self, BufRead, BufReader}, + io::{self, BufReader}, path::PathBuf, process::{Command, Stdio}, time::Instant, @@ -13,18 +11,14 @@ use std::{ use cargo_metadata::Message; use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; -use lsp_types::{ - CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, - WorkDoneProgressEnd, WorkDoneProgressReport, + +pub use cargo_metadata::diagnostic::{ + Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, }; -use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; - -pub use crate::conv::url_from_path_with_drive_lowercasing; - #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { - CargoCommand { command: String, all_targets: bool, extra_args: Vec }, + CargoCommand { command: String, all_targets: bool, all_features: bool, extra_args: Vec }, CustomCommand { command: String, args: Vec }, } @@ -62,10 +56,17 @@ pub enum CheckTask { ClearDiagnostics, /// Request adding a diagnostic with fixes included to a file - AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec }, + AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, /// Request check progress notification to client - Status(WorkDoneProgress), + Status(Status), +} + +#[derive(Debug)] +pub enum Status { + Being, + Progress(String), + End, } pub enum CheckCommand { @@ -132,9 +133,7 @@ impl FlycheckThread { fn clean_previous_results(&self, task_send: &Sender) { task_send.send(CheckTask::ClearDiagnostics).unwrap(); - task_send - .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::End)).unwrap(); } fn should_recheck(&mut self) -> bool { @@ -156,55 +155,29 @@ impl FlycheckThread { fn handle_message(&self, msg: CheckEvent, task_send: &Sender) { match msg { CheckEvent::Begin => { - task_send - .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin { - title: "Running 'cargo check'".to_string(), - cancellable: Some(false), - message: None, - percentage: None, - }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::Being)).unwrap(); } CheckEvent::End => { - task_send - .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { - message: None, - }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::End)).unwrap(); } CheckEvent::Msg(Message::CompilerArtifact(msg)) => { - task_send - .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport { - cancellable: Some(false), - message: Some(msg.target.name), - percentage: None, - }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::Progress(msg.target.name))).unwrap(); } CheckEvent::Msg(Message::CompilerMessage(msg)) => { - let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root); - if map_result.is_empty() { - return; - } - - for MappedRustDiagnostic { location, diagnostic, fixes } in map_result { - let fixes = fixes - .into_iter() - .map(|fix| { - CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into() - }) - .collect(); - - task_send - .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes }) - .unwrap(); - } + task_send + .send(CheckTask::AddDiagnostic { + workspace_root: self.workspace_root.clone(), + diagnostic: msg.message, + }) + .unwrap(); } CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} + CheckEvent::Msg(Message::BuildFinished(_)) => {} + CheckEvent::Msg(Message::TextLine(_)) => {} CheckEvent::Msg(Message::Unknown) => {} } } @@ -215,14 +188,17 @@ impl FlycheckThread { self.check_process = None; let mut cmd = match &self.config { - FlycheckConfig::CargoCommand { command, all_targets, extra_args } => { - let mut cmd = Command::new(cargo_binary()); + FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => { + let mut cmd = Command::new(ra_toolchain::cargo()); cmd.arg(command); - cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]); - cmd.arg(self.workspace_root.join("Cargo.toml")); + cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) + .arg(self.workspace_root.join("Cargo.toml")); if *all_targets { cmd.arg("--all-targets"); } + if *all_features { + cmd.arg("--all-features"); + } cmd.args(extra_args); cmd } @@ -267,12 +243,6 @@ impl FlycheckThread { } } -#[derive(Debug)] -pub struct DiagnosticWithFixes { - diagnostic: Diagnostic, - fixes: Vec, -} - enum CheckEvent { Begin, Msg(cargo_metadata::Message), @@ -296,15 +266,11 @@ fn run_cargo( // erroneus output. let stdout = BufReader::new(child.stdout.take().unwrap()); let mut read_at_least_one_message = false; - - for line in stdout.lines() { - let line = line?; - - let message = serde_json::from_str::(&line); + for message in cargo_metadata::Message::parse_stream(stdout) { let message = match message { Ok(message) => message, Err(err) => { - log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line); + log::error!("Invalid json from cargo check, ignoring ({})", err); continue; } }; @@ -334,7 +300,3 @@ fn run_cargo( Ok(()) } - -fn cargo_binary() -> String { - env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) -} diff --git a/crates/ra_fmt/src/lib.rs b/crates/ra_fmt/src/lib.rs index 1a30b2b3ab87..f910ded9da6f 100644 --- a/crates/ra_fmt/src/lib.rs +++ b/crates/ra_fmt/src/lib.rs @@ -42,7 +42,6 @@ pub fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { } pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option { - let block = block.block()?; let has_anything_else = |thing: &SyntaxNode| -> bool { let mut non_trivial_children = block.syntax().children_with_tokens().filter(|it| match it.kind() { diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index af59aa1b6cf4..840cfdfc8238 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -19,11 +19,14 @@ use hir_def::{ use hir_expand::{ diagnostics::DiagnosticSink, name::{name, AsName}, - MacroDefId, + MacroDefId, MacroDefKind, }; use hir_ty::{ - autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, - Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, + autoderef, + display::{HirDisplayError, HirFormatter}, + expr::ExprValidator, + method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty, + TyDefId, TypeCtor, }; use ra_db::{CrateId, CrateName, Edition, FileId}; use ra_prof::profile; @@ -145,6 +148,26 @@ impl ModuleDef { ModuleDef::BuiltinType(_) => None, } } + + pub fn definition_visibility(&self, db: &dyn HirDatabase) -> Option { + let module = match self { + ModuleDef::Module(it) => it.parent(db)?, + ModuleDef::Function(it) => return Some(it.visibility(db)), + ModuleDef::Adt(it) => it.module(db), + ModuleDef::EnumVariant(it) => { + let parent = it.parent_enum(db); + let module = it.module(db); + return module.visibility_of(db, &ModuleDef::Adt(Adt::Enum(parent))); + } + ModuleDef::Const(it) => return Some(it.visibility(db)), + ModuleDef::Static(it) => it.module(db), + ModuleDef::Trait(it) => it.module(db), + ModuleDef::TypeAlias(it) => return Some(it.visibility(db)), + ModuleDef::BuiltinType(_) => return None, + }; + + module.visibility_of(db, self) + } } pub use hir_def::{ @@ -675,6 +698,10 @@ impl Static { pub fn name(self, db: &dyn HirDatabase) -> Option { db.static_data(self.id).name.clone() } + + pub fn is_mut(self, db: &dyn HirDatabase) -> bool { + db.static_data(self.id).mutable + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -762,13 +789,12 @@ impl MacroDef { /// Indicate it is a proc-macro pub fn is_proc_macro(&self) -> bool { - match self.id.kind { - hir_expand::MacroDefKind::Declarative => false, - hir_expand::MacroDefKind::BuiltIn(_) => false, - hir_expand::MacroDefKind::BuiltInDerive(_) => false, - hir_expand::MacroDefKind::BuiltInEager(_) => false, - hir_expand::MacroDefKind::CustomDerive(_) => true, - } + matches!(self.id.kind, MacroDefKind::CustomDerive(_)) + } + + /// Indicate it is a derive macro + pub fn is_derive_macro(&self) -> bool { + matches!(self.id.kind, MacroDefKind::CustomDerive(_) | MacroDefKind::BuiltInDerive(_)) } } @@ -963,6 +989,17 @@ impl TypeParam { ty: InEnvironment { value: ty, environment }, } } + + pub fn default(self, db: &dyn HirDatabase) -> Option { + let params = db.generic_defaults(self.id.parent); + let local_idx = hir_ty::param_idx(db, self.id)?; + let resolver = self.id.parent.resolver(db.upcast()); + let environment = TraitEnvironment::lower(db, &resolver); + params.get(local_idx).cloned().map(|ty| Type { + krate: self.id.parent.module(db.upcast()).krate, + ty: InEnvironment { value: ty, environment }, + }) + } } // FIXME: rename from `ImplDef` to `Impl` @@ -1212,7 +1249,7 @@ impl Type { // This would be nicer if it just returned an iterator, but that runs into // lifetime problems, because we need to borrow temp `CrateImplDefs`. - pub fn iterate_impl_items( + pub fn iterate_assoc_items( self, db: &dyn HirDatabase, krate: Crate, @@ -1320,7 +1357,7 @@ impl Type { } impl HirDisplay for Type { - fn hir_fmt(&self, f: &mut HirFormatter) -> std::fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { self.ty.value.hir_fmt(f) } } diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 515e5eb1713b..7c1f79f279b6 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs @@ -23,7 +23,7 @@ use crate::{ db::HirDatabase, diagnostics::Diagnostic, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, - source_analyzer::{resolve_hir_path, SourceAnalyzer}, + source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer}, AssocItem, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, }; @@ -451,6 +451,23 @@ impl<'a, DB: HirDatabase> SemanticsScope<'a, DB> { pub fn resolve_hir_path(&self, path: &Path) -> Option { resolve_hir_path(self.db, &self.resolver, path) } + + /// Resolves a path where we know it is a qualifier of another path. + /// + /// For example, if we have: + /// ``` + /// mod my { + /// pub mod foo { + /// struct Bar; + /// } + /// + /// pub fn foo() {} + /// } + /// ``` + /// then we know that `foo` in `my::foo::Bar` refers to the module, not the function. + pub fn resolve_hir_path_qualifier(&self, path: &Path) -> Option { + resolve_hir_path_qualifier(self.db, &self.resolver, path) + } } // FIXME: Change `HasSource` trait to work with `Semantics` and remove this? diff --git a/crates/ra_hir/src/semantics/source_to_def.rs b/crates/ra_hir/src/semantics/source_to_def.rs index 6f3b5b2da869..8af64fdc1b25 100644 --- a/crates/ra_hir/src/semantics/source_to_def.rs +++ b/crates/ra_hir/src/semantics/source_to_def.rs @@ -151,7 +151,7 @@ impl SourceToDefCtx<'_, '_> { let krate = self.file_to_def(file_id)?.krate; let file_ast_id = self.db.ast_id_map(src.file_id).ast_id(&src.value); let ast_id = Some(AstId::new(src.file_id, file_ast_id)); - Some(MacroDefId { krate: Some(krate), ast_id, kind }) + Some(MacroDefId { krate: Some(krate), ast_id, kind, local_inner: false }) } pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option { diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs index c63d1b8473ef..4b509f07c6b7 100644 --- a/crates/ra_hir/src/source_analyzer.rs +++ b/crates/ra_hir/src/source_analyzer.rs @@ -226,6 +226,17 @@ impl SourceAnalyzer { // This must be a normal source file rather than macro file. let hir_path = crate::Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?; + + // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we + // trying to resolve foo::bar. + if let Some(outer_path) = path.syntax().parent().and_then(ast::Path::cast) { + if let Some(qualifier) = outer_path.qualifier() { + if path == &qualifier { + return resolve_hir_path_qualifier(db, &self.resolver, &hir_path); + } + } + } + resolve_hir_path(db, &self.resolver, &hir_path) } @@ -404,6 +415,7 @@ pub(crate) fn resolve_hir_path( TypeNs::BuiltinType(it) => PathResolution::Def(it.into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), }); + let body_owner = resolver.body_owner(); let values = resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| { @@ -417,6 +429,7 @@ pub(crate) fn resolve_hir_path( ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()), ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()), ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), + ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()), }; Some(res) }); @@ -425,9 +438,48 @@ pub(crate) fn resolve_hir_path( .resolve_module_path_in_items(db.upcast(), path.mod_path()) .take_types() .map(|it| PathResolution::Def(it.into())); + types.or(values).or(items).or_else(|| { resolver .resolve_path_as_macro(db.upcast(), path.mod_path()) .map(|def| PathResolution::Macro(def.into())) }) } + +/// Resolves a path where we know it is a qualifier of another path. +/// +/// For example, if we have: +/// ``` +/// mod my { +/// pub mod foo { +/// struct Bar; +/// } +/// +/// pub fn foo() {} +/// } +/// ``` +/// then we know that `foo` in `my::foo::Bar` refers to the module, not the function. +pub(crate) fn resolve_hir_path_qualifier( + db: &dyn HirDatabase, + resolver: &Resolver, + path: &crate::Path, +) -> Option { + let items = resolver + .resolve_module_path_in_items(db.upcast(), path.mod_path()) + .take_types() + .map(|it| PathResolution::Def(it.into())); + + if items.is_some() { + return items; + } + + resolver.resolve_path_in_type_ns_fully(db.upcast(), path.mod_path()).map(|ty| match ty { + TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), + TypeNs::GenericParam(id) => PathResolution::TypeParam(TypeParam { id }), + TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => PathResolution::Def(Adt::from(it).into()), + TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), + TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), + TypeNs::BuiltinType(it) => PathResolution::Def(it.into()), + TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), + }) +} diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index 8eef51828353..2bc34d449f2f 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs @@ -117,7 +117,14 @@ fn lower_enum( ast: &InFile, module_id: ModuleId, ) { - for var in ast.value.variant_list().into_iter().flat_map(|it| it.variants()) { + let expander = CfgExpander::new(db, ast.file_id, module_id.krate); + let variants = ast + .value + .variant_list() + .into_iter() + .flat_map(|it| it.variants()) + .filter(|var| expander.is_cfg_enabled(var)); + for var in variants { trace.alloc( || var.clone(), || EnumVariantData { @@ -209,8 +216,7 @@ fn lower_struct( match &ast.value { ast::StructKind::Tuple(fl) => { for (i, fd) in fl.fields().enumerate() { - let attrs = expander.parse_attrs(&fd); - if !expander.is_cfg_enabled(&attrs) { + if !expander.is_cfg_enabled(&fd) { continue; } @@ -227,8 +233,7 @@ fn lower_struct( } ast::StructKind::Record(fl) => { for fd in fl.fields() { - let attrs = expander.parse_attrs(&fd); - if !expander.is_cfg_enabled(&attrs) { + if !expander.is_cfg_enabled(&fd) { continue; } diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 5a86af8ba378..576cd0c65ba9 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -140,6 +140,7 @@ impl Attr { } } +#[derive(Debug, Clone, Copy)] pub struct AttrQuery<'a> { attrs: &'a Attrs, key: &'static str, diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs index 4edaad9600af..f5a7305dc090 100644 --- a/crates/ra_hir_def/src/body.rs +++ b/crates/ra_hir_def/src/body.rs @@ -60,7 +60,8 @@ impl CfgExpander { Attrs::new(owner, &self.hygiene) } - pub(crate) fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { + pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { + let attrs = self.parse_attrs(owner); attrs.is_cfg_enabled(&self.cfg_options) } } @@ -141,12 +142,8 @@ impl Expander { InFile { file_id: self.current_file_id, value } } - pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs { - self.cfg_expander.parse_attrs(owner) - } - - pub(crate) fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { - self.cfg_expander.is_cfg_enabled(attrs) + pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { + self.cfg_expander.is_cfg_enabled(owner) } fn parse_path(&mut self, path: ast::Path) -> Option { diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index e9dd65b0a436..e08d62dd6869 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs @@ -15,7 +15,7 @@ use ra_syntax::{ }, AstNode, AstPtr, }; -use test_utils::tested_by; +use test_utils::mark; use crate::{ adt::StructKind, @@ -60,13 +60,10 @@ pub(super) fn lower( params: Option, body: Option, ) -> (Body, BodySourceMap) { - let ctx = LowerCtx::new(db, expander.current_file_id.clone()); - ExprCollector { db, def, expander, - ctx, source_map: BodySourceMap::default(), body: Body { exprs: Arena::default(), @@ -83,7 +80,6 @@ struct ExprCollector<'a> { db: &'a dyn DefDatabase, def: DefWithBodyId, expander: Expander, - ctx: LowerCtx, body: Body, source_map: BodySourceMap, } @@ -122,6 +118,10 @@ impl ExprCollector<'_> { (self.body, self.source_map) } + fn ctx(&self) -> LowerCtx { + LowerCtx::new(self.db, self.expander.current_file_id) + } + fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr) -> ExprId { let src = self.expander.to_source(ptr); let id = self.make_expr(expr, Ok(src.clone())); @@ -162,8 +162,7 @@ impl ExprCollector<'_> { fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { let syntax_ptr = AstPtr::new(&expr); - let attrs = self.expander.parse_attrs(&expr); - if !self.expander.is_cfg_enabled(&attrs) { + if !self.expander.is_cfg_enabled(&expr) { return self.missing_expr(); } match expr { @@ -203,6 +202,16 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr) } + ast::Expr::EffectExpr(e) => match e.effect() { + ast::Effect::Try(_) => { + let body = self.collect_block_opt(e.block_expr()); + self.alloc_expr(Expr::TryBlock { body }, syntax_ptr) + } + // FIXME: we need to record these effects somewhere... + ast::Effect::Async(_) | ast::Effect::Label(_) | ast::Effect::Unsafe(_) => { + self.collect_block_opt(e.block_expr()) + } + }, ast::Expr::BlockExpr(e) => self.collect_block(e), ast::Expr::LoopExpr(e) => { let body = self.collect_block_opt(e.loop_body()); @@ -217,7 +226,7 @@ impl ExprCollector<'_> { None => self.collect_expr_opt(condition.expr()), // if let -- desugar to match Some(pat) => { - tested_by!(infer_resolve_while_let); + mark::hit!(infer_resolve_while_let); let pat = self.collect_pat(pat); let match_expr = self.collect_expr_opt(condition.expr()); let placeholder_pat = self.missing_pat(); @@ -259,7 +268,7 @@ impl ExprCollector<'_> { }; let method_name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); let generic_args = - e.type_arg_list().and_then(|it| GenericArgs::from_ast(&self.ctx, it)); + e.type_arg_list().and_then(|it| GenericArgs::from_ast(&self.ctx(), it)); self.alloc_expr( Expr::MethodCall { receiver, method_name, args, generic_args }, syntax_ptr, @@ -319,8 +328,7 @@ impl ExprCollector<'_> { .fields() .inspect(|field| field_ptrs.push(AstPtr::new(field))) .filter_map(|field| { - let attrs = self.expander.parse_attrs(&field); - if !self.expander.is_cfg_enabled(&attrs) { + if !self.expander.is_cfg_enabled(&field) { return None; } let name = field.field_name()?.as_name(); @@ -365,7 +373,7 @@ impl ExprCollector<'_> { } ast::Expr::CastExpr(e) => { let expr = self.collect_expr_opt(e.expr()); - let type_ref = TypeRef::from_ast_opt(&self.ctx, e.type_ref()); + let type_ref = TypeRef::from_ast_opt(&self.ctx(), e.type_ref()); self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr) } ast::Expr::RefExpr(e) => { @@ -388,7 +396,7 @@ impl ExprCollector<'_> { for param in pl.params() { let pat = self.collect_pat_opt(param.pat()); let type_ref = - param.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx, it)); + param.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx(), it)); args.push(pat); arg_types.push(type_ref); } @@ -396,7 +404,7 @@ impl ExprCollector<'_> { let ret_type = e .ret_type() .and_then(|r| r.type_ref()) - .map(|it| TypeRef::from_ast(&self.ctx, it)); + .map(|it| TypeRef::from_ast(&self.ctx(), it)); let body = self.collect_expr_opt(e.body()); self.alloc_expr(Expr::Lambda { args, arg_types, ret_type, body }, syntax_ptr) } @@ -456,6 +464,7 @@ impl ExprCollector<'_> { krate: Some(self.expander.module.krate), ast_id: Some(self.expander.ast_id(&e)), kind: MacroDefKind::Declarative, + local_inner: false, }; self.body.item_scope.define_legacy_macro(name, mac); @@ -490,19 +499,16 @@ impl ExprCollector<'_> { } } - fn collect_block(&mut self, expr: ast::BlockExpr) -> ExprId { - let syntax_node_ptr = AstPtr::new(&expr.clone().into()); - let block = match expr.block() { - Some(block) => block, - None => return self.alloc_expr(Expr::Missing, syntax_node_ptr), - }; + fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { + let syntax_node_ptr = AstPtr::new(&block.clone().into()); self.collect_block_items(&block); let statements = block .statements() .map(|s| match s { ast::Stmt::LetStmt(stmt) => { let pat = self.collect_pat_opt(stmt.pat()); - let type_ref = stmt.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx, it)); + let type_ref = + stmt.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx(), it)); let initializer = stmt.initializer().map(|e| self.collect_expr(e)); Statement::Let { pat, type_ref, initializer } } @@ -513,7 +519,7 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::Block { statements, tail }, syntax_node_ptr) } - fn collect_block_items(&mut self, block: &ast::Block) { + fn collect_block_items(&mut self, block: &ast::BlockExpr) { let container = ContainerId::DefWithBodyId(self.def); for item in block.items() { let (def, name): (ModuleDefId, Option) = match item { @@ -568,9 +574,16 @@ impl ExprCollector<'_> { self.body.item_scope.define_def(def); if let Some(name) = name { let vis = crate::visibility::Visibility::Public; // FIXME determine correctly - self.body - .item_scope - .push_res(name.as_name(), crate::per_ns::PerNs::from_def(def, vis)); + let has_constructor = match def { + ModuleDefId::AdtId(AdtId::StructId(s)) => { + self.db.struct_data(s).variant_data.kind() != StructKind::Record + } + _ => true, + }; + self.body.item_scope.push_res( + name.as_name(), + crate::per_ns::PerNs::from_def(def, vis, has_constructor), + ); } } } diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs index 86f953c802bc..09e92b74e1ab 100644 --- a/crates/ra_hir_def/src/body/scope.rs +++ b/crates/ra_hir_def/src/body/scope.rs @@ -174,7 +174,7 @@ mod tests { use hir_expand::{name::AsName, InFile}; use ra_db::{fixture::WithFixture, FileId, SourceDatabase}; use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; - use test_utils::{assert_eq_text, covers, extract_offset}; + use test_utils::{assert_eq_text, extract_offset, mark}; use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId}; @@ -388,7 +388,7 @@ mod tests { #[test] fn while_let_desugaring() { - covers!(infer_resolve_while_let); + mark::check!(infer_resolve_while_let); do_check_local_name( r#" fn test() { diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index 7a2067e49cb7..e2130d931fdb 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -9,7 +9,7 @@ use hir_expand::{ }; use ra_prof::profile; use ra_syntax::ast::{ - self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAscriptionOwner, TypeBoundsOwner, + self, AssocItem, AstNode, ModuleItemOwner, NameOwner, TypeAscriptionOwner, TypeBoundsOwner, VisibilityOwner, }; @@ -150,51 +150,31 @@ pub struct TraitData { impl TraitData { pub(crate) fn trait_data_query(db: &dyn DefDatabase, tr: TraitId) -> Arc { - let src = tr.lookup(db).source(db); + let tr_loc = tr.lookup(db); + let src = tr_loc.source(db); let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); let auto = src.value.auto_token().is_some(); - let ast_id_map = db.ast_id_map(src.file_id); + let module_id = tr_loc.container.module(db); let container = AssocContainerId::TraitId(tr); - let items = if let Some(item_list) = src.value.item_list() { - item_list - .impl_items() - .map(|item_node| match item_node { - ast::ImplItem::FnDef(it) => { - let name = it.name().map_or_else(Name::missing, |it| it.as_name()); - let def = FunctionLoc { - container, - ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), - } - .intern(db) - .into(); - (name, def) - } - ast::ImplItem::ConstDef(it) => { - let name = it.name().map_or_else(Name::missing, |it| it.as_name()); - let def = ConstLoc { - container, - ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), - } - .intern(db) - .into(); - (name, def) - } - ast::ImplItem::TypeAliasDef(it) => { - let name = it.name().map_or_else(Name::missing, |it| it.as_name()); - let def = TypeAliasLoc { - container, - ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), - } - .intern(db) - .into(); - (name, def) - } - }) - .collect() - } else { - Vec::new() - }; + let mut items = Vec::new(); + + if let Some(item_list) = src.value.item_list() { + let mut expander = Expander::new(db, tr_loc.ast_id.file_id, module_id); + items.extend(collect_items( + db, + &mut expander, + item_list.assoc_items(), + src.file_id, + container, + )); + items.extend(collect_items_in_macros( + db, + &mut expander, + &src.with_value(item_list), + container, + )); + } Arc::new(TraitData { name, items, auto }) } @@ -232,24 +212,22 @@ impl ImplData { let target_type = TypeRef::from_ast_opt(&lower_ctx, src.value.target_type()); let is_negative = src.value.excl_token().is_some(); let module_id = impl_loc.container.module(db); + let container = AssocContainerId::ImplId(id); - let mut items = Vec::new(); + let mut items: Vec = Vec::new(); if let Some(item_list) = src.value.item_list() { let mut expander = Expander::new(db, impl_loc.ast_id.file_id, module_id); - items.extend(collect_impl_items( - db, - &mut expander, - item_list.impl_items(), - src.file_id, - id, - )); - items.extend(collect_impl_items_in_macros( - db, - &mut expander, - &src.with_value(item_list), - id, - )); + items.extend( + collect_items(db, &mut expander, item_list.assoc_items(), src.file_id, container) + .into_iter() + .map(|(_, item)| item), + ); + items.extend( + collect_items_in_macros(db, &mut expander, &src.with_value(item_list), container) + .into_iter() + .map(|(_, item)| item), + ); } let res = ImplData { target_trait, target_type, items, is_negative }; @@ -273,11 +251,6 @@ impl ConstData { Arc::new(ConstData::new(db, vis_default, node)) } - pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc { - let node = konst.lookup(db).source(db); - Arc::new(ConstData::new(db, RawVisibility::private(), node)) - } - fn new( db: &dyn DefDatabase, vis_default: RawVisibility, @@ -292,49 +265,76 @@ impl ConstData { } } -fn collect_impl_items_in_macros( +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticData { + pub name: Option, + pub type_ref: TypeRef, + pub visibility: RawVisibility, + pub mutable: bool, +} + +impl StaticData { + pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc { + let node = konst.lookup(db).source(db); + let ctx = LowerCtx::new(db, node.file_id); + + let name = node.value.name().map(|n| n.as_name()); + let type_ref = TypeRef::from_ast_opt(&ctx, node.value.ascribed_type()); + let mutable = node.value.mut_token().is_some(); + let visibility = RawVisibility::from_ast_with_default( + db, + RawVisibility::private(), + node.map(|n| n.visibility()), + ); + + Arc::new(StaticData { name, type_ref, visibility, mutable }) + } +} + +fn collect_items_in_macros( db: &dyn DefDatabase, expander: &mut Expander, impl_def: &InFile, - id: ImplId, -) -> Vec { + container: AssocContainerId, +) -> Vec<(Name, AssocItemId)> { let mut res = Vec::new(); // We set a limit to protect against infinite recursion let limit = 100; for m in impl_def.value.syntax().children().filter_map(ast::MacroCall::cast) { - res.extend(collect_impl_items_in_macro(db, expander, m, id, limit)) + res.extend(collect_items_in_macro(db, expander, m, container, limit)) } res } -fn collect_impl_items_in_macro( +fn collect_items_in_macro( db: &dyn DefDatabase, expander: &mut Expander, m: ast::MacroCall, - id: ImplId, + container: AssocContainerId, limit: usize, -) -> Vec { +) -> Vec<(Name, AssocItemId)> { if limit == 0 { return Vec::new(); } if let Some((mark, items)) = expander.enter_expand(db, None, m) { let items: InFile = expander.to_source(items); - let mut res = collect_impl_items( + let mut res = collect_items( db, expander, - items.value.items().filter_map(|it| ImplItem::cast(it.syntax().clone())), + items.value.items().filter_map(|it| AssocItem::cast(it.syntax().clone())), items.file_id, - id, + container, ); + // Recursive collect macros // Note that ast::ModuleItem do not include ast::MacroCall // We cannot use ModuleItemOwner::items here for it in items.value.syntax().children().filter_map(ast::MacroCall::cast) { - res.extend(collect_impl_items_in_macro(db, expander, it, id, limit - 1)) + res.extend(collect_items_in_macro(db, expander, it, container, limit - 1)) } expander.exit(db, mark); res @@ -343,44 +343,38 @@ fn collect_impl_items_in_macro( } } -fn collect_impl_items( +fn collect_items( db: &dyn DefDatabase, expander: &mut Expander, - impl_items: impl Iterator, + assoc_items: impl Iterator, file_id: crate::HirFileId, - id: ImplId, -) -> Vec { + container: AssocContainerId, +) -> Vec<(Name, AssocItemId)> { let items = db.ast_id_map(file_id); - impl_items + assoc_items .filter_map(|item_node| match item_node { - ast::ImplItem::FnDef(it) => { - let attrs = expander.parse_attrs(&it); - if !expander.is_cfg_enabled(&attrs) { + ast::AssocItem::FnDef(it) => { + let name = it.name().map_or_else(Name::missing, |it| it.as_name()); + if !expander.is_cfg_enabled(&it) { return None; } - let def = FunctionLoc { - container: AssocContainerId::ImplId(id), - ast_id: AstId::new(file_id, items.ast_id(&it)), - } - .intern(db); - Some(def.into()) + let def = FunctionLoc { container, ast_id: AstId::new(file_id, items.ast_id(&it)) } + .intern(db); + Some((name, def.into())) } - ast::ImplItem::ConstDef(it) => { - let def = ConstLoc { - container: AssocContainerId::ImplId(id), - ast_id: AstId::new(file_id, items.ast_id(&it)), - } - .intern(db); - Some(def.into()) + ast::AssocItem::ConstDef(it) => { + let name = it.name().map_or_else(Name::missing, |it| it.as_name()); + let def = ConstLoc { container, ast_id: AstId::new(file_id, items.ast_id(&it)) } + .intern(db); + Some((name, def.into())) } - ast::ImplItem::TypeAliasDef(it) => { - let def = TypeAliasLoc { - container: AssocContainerId::ImplId(id), - ast_id: AstId::new(file_id, items.ast_id(&it)), - } - .intern(db); - Some(def.into()) + ast::AssocItem::TypeAliasDef(it) => { + let name = it.name().map_or_else(Name::missing, |it| it.as_name()); + let def = + TypeAliasLoc { container, ast_id: AstId::new(file_id, items.ast_id(&it)) } + .intern(db); + Some((name, def.into())) } }) .collect() diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs index 5dc7395f5e80..945a0025e504 100644 --- a/crates/ra_hir_def/src/db.rs +++ b/crates/ra_hir_def/src/db.rs @@ -1,7 +1,7 @@ //! Defines database & queries for name resolution. use std::sync::Arc; -use hir_expand::{db::AstDatabase, HirFileId}; +use hir_expand::{db::AstDatabase, name::Name, HirFileId}; use ra_db::{salsa, CrateId, SourceDatabase, Upcast}; use ra_prof::profile; use ra_syntax::SmolStr; @@ -10,11 +10,15 @@ use crate::{ adt::{EnumData, StructData}, attr::Attrs, body::{scope::ExprScopes, Body, BodySourceMap}, - data::{ConstData, FunctionData, ImplData, TraitData, TypeAliasData}, + data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData}, docs::Documentation, + find_path, generics::GenericParams, + item_scope::ItemInNs, lang_item::{LangItemTarget, LangItems}, nameres::{raw::RawItems, CrateDefMap}, + path::ModPath, + visibility::Visibility, AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, @@ -77,8 +81,8 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[salsa::invoke(ConstData::const_data_query)] fn const_data(&self, konst: ConstId) -> Arc; - #[salsa::invoke(ConstData::static_data_query)] - fn static_data(&self, konst: StaticId) -> Arc; + #[salsa::invoke(StaticData::static_data_query)] + fn static_data(&self, konst: StaticId) -> Arc; #[salsa::invoke(Body::body_with_source_map_query)] fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc, Arc); @@ -108,6 +112,16 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { // Remove this query completely, in favor of `Attrs::docs` method #[salsa::invoke(Documentation::documentation_query)] fn documentation(&self, def: AttrDefId) -> Option; + + #[salsa::invoke(find_path::importable_locations_of_query)] + fn importable_locations_of( + &self, + item: ItemInNs, + krate: CrateId, + ) -> Arc<[(ModuleId, Name, Visibility)]>; + + #[salsa::invoke(find_path::find_path_inner_query)] + fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option; } fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc { diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs index aad12e1235d3..a0cdad529b3f 100644 --- a/crates/ra_hir_def/src/expr.rs +++ b/crates/ra_hir_def/src/expr.rs @@ -101,6 +101,9 @@ pub enum Expr { Try { expr: ExprId, }, + TryBlock { + body: ExprId, + }, Cast { expr: ExprId, type_ref: TypeRef, @@ -236,6 +239,7 @@ impl Expr { f(*expr); } } + Expr::TryBlock { body } => f(*body), Expr::Loop { body } => f(*body), Expr::While { condition, body } => { f(*condition); diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs index 70dcb03e6e37..4db7984730aa 100644 --- a/crates/ra_hir_def/src/find_path.rs +++ b/crates/ra_hir_def/src/find_path.rs @@ -1,5 +1,11 @@ //! An algorithm to find a path to refer to a certain item. +use std::sync::Arc; + +use hir_expand::name::{known, AsName, Name}; +use ra_prof::profile; +use test_utils::mark; + use crate::{ db::DefDatabase, item_scope::ItemInNs, @@ -7,25 +13,28 @@ use crate::{ visibility::Visibility, CrateId, ModuleDefId, ModuleId, }; -use hir_expand::name::{known, AsName, Name}; -use test_utils::tested_by; + +// FIXME: handle local items + +/// Find a path that can be used to refer to a certain item. This can depend on +/// *from where* you're referring to the item, hence the `from` parameter. +pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option { + let _p = profile("find_path"); + db.find_path_inner(item, from, MAX_PATH_LEN) +} const MAX_PATH_LEN: usize = 15; impl ModPath { fn starts_with_std(&self) -> bool { - self.segments.first().filter(|&first_segment| first_segment == &known::std).is_some() + self.segments.first() == Some(&known::std) } // When std library is present, paths starting with `std::` // should be preferred over paths starting with `core::` and `alloc::` fn can_start_with_std(&self) -> bool { - self.segments - .first() - .filter(|&first_segment| { - first_segment == &known::alloc || first_segment == &known::core - }) - .is_some() + let first_segment = self.segments.first(); + first_segment == Some(&known::alloc) || first_segment == Some(&known::core) } fn len(&self) -> usize { @@ -40,15 +49,7 @@ impl ModPath { } } -// FIXME: handle local items - -/// Find a path that can be used to refer to a certain item. This can depend on -/// *from where* you're referring to the item, hence the `from` parameter. -pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option { - find_path_inner(db, item, from, MAX_PATH_LEN) -} - -fn find_path_inner( +pub(crate) fn find_path_inner_query( db: &dyn DefDatabase, item: ItemInNs, from: ModuleId, @@ -139,8 +140,7 @@ fn find_path_inner( let mut best_path = None; let mut best_path_len = max_len; for (module_id, name) in importable_locations { - let mut path = match find_path_inner( - db, + let mut path = match db.find_path_inner( ItemInNs::Types(ModuleDefId::ModuleId(module_id)), from, best_path_len - 1, @@ -163,17 +163,19 @@ fn find_path_inner( fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { if old_path.starts_with_std() && new_path.can_start_with_std() { - tested_by!(prefer_std_paths); if prefer_no_std { + mark::hit!(prefer_no_std_paths); new_path } else { + mark::hit!(prefer_std_paths); old_path } } else if new_path.starts_with_std() && old_path.can_start_with_std() { - tested_by!(prefer_std_paths); if prefer_no_std { + mark::hit!(prefer_no_std_paths); old_path } else { + mark::hit!(prefer_std_paths); new_path } } else if new_path.len() < old_path.len() { @@ -198,7 +200,7 @@ fn find_importable_locations( .chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id)) { result.extend( - importable_locations_in_crate(db, item, krate) + db.importable_locations_of(item, krate) .iter() .filter(|(_, _, vis)| vis.is_visible_from(db, from)) .map(|(m, n, _)| (*m, n.clone())), @@ -213,11 +215,12 @@ fn find_importable_locations( /// /// Note that the crate doesn't need to be the one in which the item is defined; /// it might be re-exported in other crates. -fn importable_locations_in_crate( +pub(crate) fn importable_locations_of_query( db: &dyn DefDatabase, item: ItemInNs, krate: CrateId, -) -> Vec<(ModuleId, Name, Visibility)> { +) -> Arc<[(ModuleId, Name, Visibility)]> { + let _p = profile("importable_locations_of_query"); let def_map = db.crate_def_map(krate); let mut result = Vec::new(); for (local_id, data) in def_map.modules.iter() { @@ -243,17 +246,20 @@ fn importable_locations_in_crate( result.push((ModuleId { krate, local_id }, name.clone(), vis)); } } - result + + Arc::from(result) } #[cfg(test)] mod tests { - use super::*; - use crate::test_db::TestDB; use hir_expand::hygiene::Hygiene; use ra_db::fixture::WithFixture; use ra_syntax::ast::AstNode; - use test_utils::covers; + use test_utils::mark; + + use crate::test_db::TestDB; + + use super::*; /// `code` needs to contain a cursor marker; checks that `find_path` for the /// item the `path` refers to returns that same path when called from the @@ -508,7 +514,7 @@ mod tests { #[test] fn prefer_std_paths_over_alloc() { - covers!(prefer_std_paths); + mark::check!(prefer_std_paths); let code = r#" //- /main.rs crate:main deps:alloc,std <|> @@ -526,33 +532,9 @@ mod tests { check_found_path(code, "std::sync::Arc"); } - #[test] - fn prefer_alloc_paths_over_std() { - covers!(prefer_std_paths); - let code = r#" - //- /main.rs crate:main deps:alloc,std - #![no_std] - - <|> - - //- /std.rs crate:std deps:alloc - - pub mod sync { - pub use alloc::sync::Arc; - } - - //- /zzz.rs crate:alloc - - pub mod sync { - pub struct Arc; - } - "#; - check_found_path(code, "alloc::sync::Arc"); - } - #[test] fn prefer_core_paths_over_std() { - covers!(prefer_std_paths); + mark::check!(prefer_no_std_paths); let code = r#" //- /main.rs crate:main deps:core,std #![no_std] @@ -574,6 +556,29 @@ mod tests { check_found_path(code, "core::fmt::Error"); } + #[test] + fn prefer_alloc_paths_over_std() { + let code = r#" + //- /main.rs crate:main deps:alloc,std + #![no_std] + + <|> + + //- /std.rs crate:std deps:alloc + + pub mod sync { + pub use alloc::sync::Arc; + } + + //- /zzz.rs crate:alloc + + pub mod sync { + pub struct Arc; + } + "#; + check_found_path(code, "alloc::sync::Arc"); + } + #[test] fn prefer_shorter_paths_if_not_alloc() { let code = r#" diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs index 259b9ff035d1..fc15948adf4d 100644 --- a/crates/ra_hir_def/src/item_scope.rs +++ b/crates/ra_hir_def/src/item_scope.rs @@ -151,13 +151,20 @@ impl ItemScope { } impl PerNs { - pub(crate) fn from_def(def: ModuleDefId, v: Visibility) -> PerNs { + pub(crate) fn from_def(def: ModuleDefId, v: Visibility, has_constructor: bool) -> PerNs { match def { ModuleDefId::ModuleId(_) => PerNs::types(def, v), ModuleDefId::FunctionId(_) => PerNs::values(def, v), ModuleDefId::AdtId(adt) => match adt { - AdtId::StructId(_) | AdtId::UnionId(_) => PerNs::both(def, def, v), + AdtId::UnionId(_) => PerNs::types(def, v), AdtId::EnumId(_) => PerNs::types(def, v), + AdtId::StructId(_) => { + if has_constructor { + PerNs::both(def, def, v) + } else { + PerNs::types(def, v) + } + } }, ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v), ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v), diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 518772e8abd6..5325a27608ea 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -46,8 +46,6 @@ pub mod find_path; #[cfg(test)] mod test_db; -#[cfg(test)] -mod marks; use std::hash::Hash; diff --git a/crates/ra_hir_def/src/marks.rs b/crates/ra_hir_def/src/marks.rs deleted file mode 100644 index daa49d5f1040..000000000000 --- a/crates/ra_hir_def/src/marks.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! See test_utils/src/marks.rs - -test_utils::marks!( - bogus_paths - name_res_works_for_broken_modules - can_import_enum_variant - glob_enum - glob_enum_group - glob_across_crates - std_prelude - macro_rules_from_other_crates_are_visible_with_macro_use - prelude_is_macro_use - macro_dollar_crate_self - macro_dollar_crate_other - infer_resolve_while_let - prefer_std_paths -); diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 98c74fe257b4..353a31ad47a1 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -14,7 +14,7 @@ use ra_cfg::CfgOptions; use ra_db::{CrateId, FileId, ProcMacroId}; use ra_syntax::ast; use rustc_hash::FxHashMap; -use test_utils::tested_by; +use test_utils::mark; use crate::{ attr::Attrs, @@ -204,6 +204,7 @@ impl DefCollector<'_> { ast_id: None, krate: Some(krate), kind: MacroDefKind::CustomDerive(expander), + local_inner: false, }; self.define_proc_macro(name.clone(), macro_id); @@ -301,7 +302,7 @@ impl DefCollector<'_> { ); if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { - tested_by!(macro_rules_from_other_crates_are_visible_with_macro_use); + mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); self.import_all_macros_exported(current_module_id, m.krate); } } @@ -411,10 +412,10 @@ impl DefCollector<'_> { match def.take_types() { Some(ModuleDefId::ModuleId(m)) => { if import.is_prelude { - tested_by!(std_prelude); + mark::hit!(std_prelude); self.def_map.prelude = Some(m); } else if m.krate != self.def_map.krate { - tested_by!(glob_across_crates); + mark::hit!(glob_across_crates); // glob import from other crate => we can just import everything once let item_map = self.db.crate_def_map(m.krate); let scope = &item_map[m.local_id].scope; @@ -460,7 +461,7 @@ impl DefCollector<'_> { } } Some(ModuleDefId::AdtId(AdtId::EnumId(e))) => { - tested_by!(glob_enum); + mark::hit!(glob_enum); // glob import from enum => just import all the variants // XXX: urgh, so this works by accident! Here, we look at @@ -509,7 +510,7 @@ impl DefCollector<'_> { self.update(module_id, &[(name, def)], vis); } - None => tested_by!(bogus_paths), + None => mark::hit!(bogus_paths), } } } @@ -682,7 +683,7 @@ impl ModCollector<'_, '_> { // Prelude module is always considered to be `#[macro_use]`. if let Some(prelude_module) = self.def_collector.def_map.prelude { if prelude_module.krate != self.def_collector.def_map.krate { - tested_by!(prelude_is_macro_use); + mark::hit!(prelude_is_macro_use); self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); } } @@ -829,7 +830,7 @@ impl ModCollector<'_, '_> { let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res }; let def: ModuleDefId = module.into(); self.def_collector.def_map.modules[self.module_id].scope.define_def(def); - self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis))], vis); + self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis, false))], vis); res } @@ -843,6 +844,8 @@ impl ModCollector<'_, '_> { let name = def.name.clone(); let container = ContainerId::ModuleId(module); let vis = &def.visibility; + let mut has_constructor = false; + let def: ModuleDefId = match def.kind { raw::DefKind::Function(ast_id) => FunctionLoc { container: container.into(), @@ -850,7 +853,8 @@ impl ModCollector<'_, '_> { } .intern(self.def_collector.db) .into(), - raw::DefKind::Struct(ast_id) => { + raw::DefKind::Struct(ast_id, mode) => { + has_constructor = mode != raw::StructDefKind::Record; StructLoc { container, ast_id: AstId::new(self.file_id, ast_id) } .intern(self.def_collector.db) .into() @@ -893,7 +897,11 @@ impl ModCollector<'_, '_> { .def_map .resolve_visibility(self.def_collector.db, self.module_id, vis) .unwrap_or(Visibility::Public); - self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis))], vis) + self.def_collector.update( + self.module_id, + &[(name, PerNs::from_def(def, vis, has_constructor))], + vis, + ) } fn collect_derives(&mut self, attrs: &Attrs, def: &raw::DefData) { @@ -941,6 +949,7 @@ impl ModCollector<'_, '_> { ast_id: Some(ast_id.ast_id), krate: Some(self.def_collector.def_map.krate), kind: MacroDefKind::Declarative, + local_inner: mac.local_inner, }; self.def_collector.define_macro(self.module_id, name.clone(), macro_id, mac.export); } diff --git a/crates/ra_hir_def/src/nameres/path_resolution.rs b/crates/ra_hir_def/src/nameres/path_resolution.rs index 35a0a0c9885e..19692e70cf45 100644 --- a/crates/ra_hir_def/src/nameres/path_resolution.rs +++ b/crates/ra_hir_def/src/nameres/path_resolution.rs @@ -14,7 +14,7 @@ use std::iter::successors; use hir_expand::name::Name; use ra_db::Edition; -use test_utils::tested_by; +use test_utils::mark; use crate::{ db::DefDatabase, @@ -108,7 +108,7 @@ impl CrateDefMap { let mut curr_per_ns: PerNs = match path.kind { PathKind::DollarCrate(krate) => { if krate == self.krate { - tested_by!(macro_dollar_crate_self); + mark::hit!(macro_dollar_crate_self); PerNs::types( ModuleId { krate: self.krate, local_id: self.root }.into(), Visibility::Public, @@ -116,7 +116,7 @@ impl CrateDefMap { } else { let def_map = db.crate_def_map(krate); let module = ModuleId { krate, local_id: def_map.root }; - tested_by!(macro_dollar_crate_other); + mark::hit!(macro_dollar_crate_other); PerNs::types(module.into(), Visibility::Public) } } @@ -221,7 +221,7 @@ impl CrateDefMap { } ModuleDefId::AdtId(AdtId::EnumId(e)) => { // enum variant - tested_by!(can_import_enum_variant); + mark::hit!(can_import_enum_variant); let enum_data = db.enum_data(e); match enum_data.variant(&segment) { Some(local_id) => { diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs index 39b011ad724c..4e628b14d921 100644 --- a/crates/ra_hir_def/src/nameres/raw.rs +++ b/crates/ra_hir_def/src/nameres/raw.rs @@ -18,7 +18,7 @@ use ra_syntax::{ ast::{self, AttrsOwner, NameOwner, VisibilityOwner}, AstNode, }; -use test_utils::tested_by; +use test_utils::mark; use crate::{ attr::Attrs, @@ -155,10 +155,17 @@ pub(super) struct DefData { pub(super) visibility: RawVisibility, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub(super) enum StructDefKind { + Record, + Tuple, + Unit, +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(super) enum DefKind { Function(FileAstId), - Struct(FileAstId), + Struct(FileAstId, StructDefKind), Union(FileAstId), Enum(FileAstId), Const(FileAstId), @@ -171,7 +178,7 @@ impl DefKind { pub fn ast_id(&self) -> FileAstId { match self { DefKind::Function(it) => it.upcast(), - DefKind::Struct(it) => it.upcast(), + DefKind::Struct(it, _) => it.upcast(), DefKind::Union(it) => it.upcast(), DefKind::Enum(it) => it.upcast(), DefKind::Const(it) => it.upcast(), @@ -188,6 +195,7 @@ pub(super) struct MacroData { pub(super) path: ModPath, pub(super) name: Option, pub(super) export: bool, + pub(super) local_inner: bool, pub(super) builtin: bool, } @@ -235,9 +243,14 @@ impl RawItemsCollector { return; } ast::ModuleItem::StructDef(it) => { + let kind = match it.kind() { + ast::StructKind::Record(_) => StructDefKind::Record, + ast::StructKind::Tuple(_) => StructDefKind::Tuple, + ast::StructKind::Unit => StructDefKind::Unit, + }; let id = self.source_ast_id_map.ast_id(&it); let name = it.name(); - (DefKind::Struct(id), name) + (DefKind::Struct(id, kind), name) } ast::ModuleItem::UnionDef(it) => { let id = self.source_ast_id_map.ast_id(&it); @@ -333,7 +346,7 @@ impl RawItemsCollector { self.push_item(current_module, attrs, RawItemKind::Module(item)); return; } - tested_by!(name_res_works_for_broken_modules); + mark::hit!(name_res_works_for_broken_modules); } fn add_use_item(&mut self, current_module: Option>, use_item: ast::UseItem) { @@ -401,14 +414,32 @@ impl RawItemsCollector { let name = m.name().map(|it| it.as_name()); let ast_id = self.source_ast_id_map.ast_id(&m); - // FIXME: cfg_attr - let export = m.attrs().filter_map(|x| x.simple_name()).any(|name| name == "macro_export"); // FIXME: cfg_attr - let builtin = - m.attrs().filter_map(|x| x.simple_name()).any(|name| name == "rustc_builtin_macro"); + let export_attr = attrs.by_key("macro_export"); - let m = self.raw_items.macros.alloc(MacroData { ast_id, path, name, export, builtin }); + let export = export_attr.exists(); + let local_inner = if export { + export_attr.tt_values().map(|it| &it.token_trees).flatten().any(|it| match it { + tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => { + ident.text.contains("local_inner_macros") + } + _ => false, + }) + } else { + false + }; + + let builtin = attrs.by_key("rustc_builtin_macro").exists(); + + let m = self.raw_items.macros.alloc(MacroData { + ast_id, + path, + name, + export, + local_inner, + builtin, + }); self.push_item(current_module, attrs, RawItemKind::Macro(m)); } diff --git a/crates/ra_hir_def/src/nameres/tests.rs b/crates/ra_hir_def/src/nameres/tests.rs index 83120fa365d6..05cd0297d1ed 100644 --- a/crates/ra_hir_def/src/nameres/tests.rs +++ b/crates/ra_hir_def/src/nameres/tests.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use insta::assert_snapshot; use ra_db::{fixture::WithFixture, SourceDatabase}; -use test_utils::covers; +use test_utils::mark; use crate::{db::DefDatabase, nameres::*, test_db::TestDB}; @@ -67,7 +67,7 @@ fn crate_def_map_smoke_test() { ⋮Baz: t v ⋮E: t ⋮EXT: v - ⋮U: t v + ⋮U: t ⋮ext: v "###) } @@ -132,7 +132,7 @@ fn crate_def_map_fn_mod_same_name() { #[test] fn bogus_paths() { - covers!(bogus_paths); + mark::check!(bogus_paths); let map = def_map( " //- /lib.rs @@ -247,7 +247,7 @@ fn re_exports() { #[test] fn std_prelude() { - covers!(std_prelude); + mark::check!(std_prelude); let map = def_map( " //- /main.rs crate:main deps:test_crate @@ -271,7 +271,7 @@ fn std_prelude() { #[test] fn can_import_enum_variant() { - covers!(can_import_enum_variant); + mark::check!(can_import_enum_variant); let map = def_map( " //- /lib.rs diff --git a/crates/ra_hir_def/src/nameres/tests/globs.rs b/crates/ra_hir_def/src/nameres/tests/globs.rs index ee8df3a26f16..2b12c0daad5a 100644 --- a/crates/ra_hir_def/src/nameres/tests/globs.rs +++ b/crates/ra_hir_def/src/nameres/tests/globs.rs @@ -152,7 +152,7 @@ fn glob_privacy_2() { #[test] fn glob_across_crates() { - covers!(glob_across_crates); + mark::check!(glob_across_crates); let map = def_map( r" //- /main.rs crate:main deps:test_crate @@ -171,7 +171,6 @@ fn glob_across_crates() { #[test] fn glob_privacy_across_crates() { - covers!(glob_across_crates); let map = def_map( r" //- /main.rs crate:main deps:test_crate @@ -191,7 +190,7 @@ fn glob_privacy_across_crates() { #[test] fn glob_enum() { - covers!(glob_enum); + mark::check!(glob_enum); let map = def_map( " //- /lib.rs @@ -212,7 +211,7 @@ fn glob_enum() { #[test] fn glob_enum_group() { - covers!(glob_enum_group); + mark::check!(glob_enum_group); let map = def_map( r" //- /lib.rs diff --git a/crates/ra_hir_def/src/nameres/tests/macros.rs b/crates/ra_hir_def/src/nameres/tests/macros.rs index b0befdfbd614..84480d9f6c4b 100644 --- a/crates/ra_hir_def/src/nameres/tests/macros.rs +++ b/crates/ra_hir_def/src/nameres/tests/macros.rs @@ -19,12 +19,12 @@ fn macro_rules_are_globally_visible() { ); assert_snapshot!(map, @r###" ⋮crate - ⋮Foo: t v + ⋮Foo: t ⋮nested: t ⋮ ⋮crate::nested - ⋮Bar: t v - ⋮Baz: t v + ⋮Bar: t + ⋮Baz: t "###); } @@ -91,13 +91,13 @@ fn macro_rules_from_other_crates_are_visible() { ); assert_snapshot!(map, @r###" ⋮crate - ⋮Bar: t v - ⋮Foo: t v + ⋮Bar: t + ⋮Foo: t ⋮bar: t ⋮ ⋮crate::bar - ⋮Bar: t v - ⋮Foo: t v + ⋮Bar: t + ⋮Foo: t ⋮bar: t "###); } @@ -124,13 +124,50 @@ fn macro_rules_export_with_local_inner_macros_are_visible() { ); assert_snapshot!(map, @r###" ⋮crate - ⋮Bar: t v - ⋮Foo: t v + ⋮Bar: t + ⋮Foo: t ⋮bar: t ⋮ ⋮crate::bar - ⋮Bar: t v - ⋮Foo: t v + ⋮Bar: t + ⋮Foo: t + ⋮bar: t + "###); +} + +#[test] +fn local_inner_macros_makes_local_macros_usable() { + let map = def_map( + " + //- /main.rs crate:main deps:foo + foo::structs!(Foo, Bar); + mod bar; + //- /bar.rs + use crate::*; + //- /lib.rs crate:foo + #[macro_export(local_inner_macros)] + macro_rules! structs { + ($($i:ident),*) => { + inner!($($i),*); + } + } + #[macro_export] + macro_rules! inner { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } + } + ", + ); + assert_snapshot!(map, @r###" + ⋮crate + ⋮Bar: t + ⋮Foo: t + ⋮bar: t + ⋮ + ⋮crate::bar + ⋮Bar: t + ⋮Foo: t ⋮bar: t "###); } @@ -167,7 +204,7 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() { ); assert_snapshot!(map, @r###" ⋮crate - ⋮Foo: t v + ⋮Foo: t ⋮bar: m ⋮foo: m "###); @@ -175,7 +212,7 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() { #[test] fn macro_rules_from_other_crates_are_visible_with_macro_use() { - covers!(macro_rules_from_other_crates_are_visible_with_macro_use); + mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use); let map = def_map( " //- /main.rs crate:main deps:foo @@ -225,7 +262,7 @@ fn macro_rules_from_other_crates_are_visible_with_macro_use() { #[test] fn prelude_is_macro_use() { - covers!(prelude_is_macro_use); + mark::check!(prelude_is_macro_use); let map = def_map( " //- /main.rs crate:main deps:foo @@ -507,8 +544,7 @@ fn path_qualified_macros() { #[test] fn macro_dollar_crate_is_correct_in_item() { - covers!(macro_dollar_crate_self); - covers!(macro_dollar_crate_other); + mark::check!(macro_dollar_crate_self); let map = def_map( " //- /main.rs crate:main deps:foo @@ -566,7 +602,7 @@ fn macro_dollar_crate_is_correct_in_item() { #[test] fn macro_dollar_crate_is_correct_in_indirect_deps() { - covers!(macro_dollar_crate_other); + mark::check!(macro_dollar_crate_other); // From std let map = def_map( r#" diff --git a/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs index 37fcdfb8cc1b..b43b294cab7c 100644 --- a/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs @@ -2,7 +2,7 @@ use super::*; #[test] fn name_res_works_for_broken_modules() { - covers!(name_res_works_for_broken_modules); + mark::check!(name_res_works_for_broken_modules); let map = def_map( r" //- /lib.rs diff --git a/crates/ra_hir_def/src/path/lower.rs b/crates/ra_hir_def/src/path/lower.rs index e3d237a0acb9..6a0c019fdff9 100644 --- a/crates/ra_hir_def/src/path/lower.rs +++ b/crates/ra_hir_def/src/path/lower.rs @@ -116,6 +116,21 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option } segments.reverse(); generic_args.reverse(); + + // handle local_inner_macros : + // Basically, even in rustc it is quite hacky: + // https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456 + // We follow what it did anyway :) + if segments.len() == 1 && kind == PathKind::Plain { + if let Some(macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) { + if macro_call.is_bang() { + if let Some(crate_id) = hygiene.local_inner_macros() { + kind = PathKind::DollarCrate(crate_id); + } + } + } + } + let mod_path = ModPath { kind, segments }; return Some(Path { type_anchor, mod_path, generic_args }); diff --git a/crates/ra_hir_def/src/path/lower/lower_use.rs b/crates/ra_hir_def/src/path/lower/lower_use.rs index 5b6854b0f003..7cc655487e78 100644 --- a/crates/ra_hir_def/src/path/lower/lower_use.rs +++ b/crates/ra_hir_def/src/path/lower/lower_use.rs @@ -6,7 +6,7 @@ use std::iter; use either::Either; use hir_expand::{hygiene::Hygiene, name::AsName}; use ra_syntax::ast::{self, NameOwner}; -use test_utils::tested_by; +use test_utils::mark; use crate::path::{ImportAlias, ModPath, PathKind}; @@ -54,7 +54,7 @@ pub(crate) fn lower_use_tree( // FIXME: report errors somewhere // We get here if we do } else if is_glob { - tested_by!(glob_enum_group); + mark::hit!(glob_enum_group); if let Some(prefix) = prefix { cb(prefix, &tree, is_glob, None) } diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs index 717506358f98..15fdd9019b73 100644 --- a/crates/ra_hir_def/src/resolver.rs +++ b/crates/ra_hir_def/src/resolver.rs @@ -86,6 +86,7 @@ pub enum ResolveValueResult { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ValueNs { + ImplSelf(ImplId), LocalBinding(PatId), FunctionId(FunctionId), ConstId(ConstId), @@ -291,19 +292,26 @@ impl Resolver { } Scope::GenericParams { .. } => continue, - Scope::ImplDefScope(impl_) if n_segments > 1 => { + Scope::ImplDefScope(impl_) => { if first_name == &name![Self] { - let ty = TypeNs::SelfType(*impl_); - return Some(ResolveValueResult::Partial(ty, 1)); + if n_segments > 1 { + let ty = TypeNs::SelfType(*impl_); + return Some(ResolveValueResult::Partial(ty, 1)); + } else { + return Some(ResolveValueResult::ValueNs(ValueNs::ImplSelf(*impl_))); + } } } - Scope::AdtScope(adt) if n_segments > 1 => { + Scope::AdtScope(adt) => { + if n_segments == 1 { + // bare `Self` doesn't work in the value namespace in a struct/enum definition + continue; + } if first_name == &name![Self] { let ty = TypeNs::AdtSelfType(*adt); return Some(ResolveValueResult::Partial(ty, 1)); } } - Scope::ImplDefScope(_) | Scope::AdtScope(_) => continue, Scope::ModuleScope(m) => { let (module_def, idx) = m.crate_def_map.resolve_path( diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs index e60f879a3935..1dc9cac6651e 100644 --- a/crates/ra_hir_expand/src/builtin_derive.rs +++ b/crates/ra_hir_expand/src/builtin_derive.rs @@ -38,7 +38,7 @@ macro_rules! register_builtin { _ => return None, }; - Some(MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(kind) }) + Some(MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(kind), local_inner: false }) } }; } diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index e0fef613db01..3bce8f673fd1 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs @@ -73,11 +73,13 @@ pub fn find_builtin_macro( krate: Some(krate), ast_id: Some(ast_id), kind: MacroDefKind::BuiltIn(kind), + local_inner: false, }), Either::Right(kind) => Some(MacroDefId { krate: Some(krate), ast_id: Some(ast_id), kind: MacroDefKind::BuiltInEager(kind), + local_inner: false, }), } } @@ -358,7 +360,7 @@ fn env_expand( // However, we cannot use an empty string here, because for // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become // `include!("foo.rs"), which might go to infinite loop - let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTATED__".to_string()); + let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string()); let expanded = quote! { #s }; Ok((expanded, FragmentKind::Expr)) @@ -406,6 +408,7 @@ mod tests { krate: Some(CrateId(0)), ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), kind: MacroDefKind::BuiltIn(expander), + local_inner: false, }; let loc = MacroCallLoc { @@ -425,6 +428,7 @@ mod tests { krate: Some(CrateId(0)), ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), kind: MacroDefKind::BuiltInEager(expander), + local_inner: false, }; let args = macro_calls[1].token_tree().unwrap(); @@ -504,7 +508,7 @@ mod tests { "#, ); - assert_eq!(expanded, "\"__RA_UNIMPLEMENTATED__\""); + assert_eq!(expanded, "\"__RA_UNIMPLEMENTED__\""); } #[test] diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index 0474523063cb..bf30d71519e1 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs @@ -34,7 +34,12 @@ impl TokenExpander { // FIXME switch these to ExpandResult as well TokenExpander::Builtin(it) => it.expand(db, id, tt).into(), TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(), - TokenExpander::ProcMacro(it) => it.expand(db, id, tt).into(), + TokenExpander::ProcMacro(_) => { + // We store the result in salsa db to prevent non-determinisc behavior in + // some proc-macro implementation + // See #4315 for details + db.expand_proc_macro(id.into()).into() + } } } @@ -75,6 +80,8 @@ pub trait AstDatabase: SourceDatabase { #[salsa::interned] fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; + + fn expand_proc_macro(&self, call: MacroCallId) -> Result; } /// This expands the given macro call, but with different arguments. This is @@ -216,6 +223,33 @@ fn macro_expand_with_arg( (Some(Arc::new(tt)), err.map(|e| format!("{:?}", e))) } +pub(crate) fn expand_proc_macro( + db: &dyn AstDatabase, + id: MacroCallId, +) -> Result { + let lazy_id = match id { + MacroCallId::LazyMacro(id) => id, + MacroCallId::EagerMacro(_) => unreachable!(), + }; + + let loc = db.lookup_intern_macro(lazy_id); + let macro_arg = match db.macro_arg(id) { + Some(it) => it, + None => { + return Err( + tt::ExpansionError::Unknown("No arguments for proc-macro".to_string()).into() + ) + } + }; + + let expander = match loc.def.kind { + MacroDefKind::CustomDerive(expander) => expander, + _ => unreachable!(), + }; + + expander.expand(db, lazy_id, ¯o_arg.0) +} + pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option { match file_id.0 { HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), @@ -330,7 +364,7 @@ fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind { FragmentKind::Expr } // FIXME: Expand to statements in appropriate positions; HIR lowering needs to handle that - EXPR_STMT | BLOCK => FragmentKind::Expr, + EXPR_STMT | BLOCK_EXPR => FragmentKind::Expr, ARG_LIST => FragmentKind::Expr, TRY_EXPR => FragmentKind::Expr, TUPLE_EXPR => FragmentKind::Expr, @@ -342,7 +376,6 @@ fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind { CONDITION => FragmentKind::Expr, BREAK_EXPR => FragmentKind::Expr, RETURN_EXPR => FragmentKind::Expr, - BLOCK_EXPR => FragmentKind::Expr, MATCH_EXPR => FragmentKind::Expr, MATCH_ARM => FragmentKind::Expr, MATCH_GUARD => FragmentKind::Expr, diff --git a/crates/ra_hir_expand/src/hygiene.rs b/crates/ra_hir_expand/src/hygiene.rs index 53866bbcb7c1..6b482a60c540 100644 --- a/crates/ra_hir_expand/src/hygiene.rs +++ b/crates/ra_hir_expand/src/hygiene.rs @@ -16,31 +16,34 @@ use crate::{ pub struct Hygiene { // This is what `$crate` expands to def_crate: Option, + + // Indiciate this is a local inner macro + local_inner: bool, } impl Hygiene { pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { - let def_crate = match file_id.0 { - HirFileIdRepr::FileId(_) => None, + let (def_crate, local_inner) = match file_id.0 { + HirFileIdRepr::FileId(_) => (None, false), HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id { MacroCallId::LazyMacro(id) => { let loc = db.lookup_intern_macro(id); match loc.def.kind { - MacroDefKind::Declarative => loc.def.krate, - MacroDefKind::BuiltIn(_) => None, - MacroDefKind::BuiltInDerive(_) => None, - MacroDefKind::BuiltInEager(_) => None, - MacroDefKind::CustomDerive(_) => None, + MacroDefKind::Declarative => (loc.def.krate, loc.def.local_inner), + MacroDefKind::BuiltIn(_) => (None, false), + MacroDefKind::BuiltInDerive(_) => (None, false), + MacroDefKind::BuiltInEager(_) => (None, false), + MacroDefKind::CustomDerive(_) => (None, false), } } - MacroCallId::EagerMacro(_id) => None, + MacroCallId::EagerMacro(_id) => (None, false), }, }; - Hygiene { def_crate } + Hygiene { def_crate, local_inner } } pub fn new_unhygienic() -> Hygiene { - Hygiene { def_crate: None } + Hygiene { def_crate: None, local_inner: false } } // FIXME: this should just return name @@ -52,4 +55,12 @@ impl Hygiene { } Either::Left(name_ref.as_name()) } + + pub fn local_inner_macros(&self) -> Option { + if self.local_inner { + self.def_crate + } else { + None + } + } } diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 754a0f005bae..f440c073ba8a 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs @@ -204,6 +204,8 @@ pub struct MacroDefId { pub krate: Option, pub ast_id: Option>, pub kind: MacroDefKind, + + pub local_inner: bool, } impl MacroDefId { diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 65db6d1b0888..5fc0ec5e3c7c 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -27,9 +27,9 @@ test_utils = { path = "../test_utils" } scoped-tls = "1" -chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "2c072cc830d04af5f10b390e6643327f85108282" } -chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "2c072cc830d04af5f10b390e6643327f85108282" } -chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "2c072cc830d04af5f10b390e6643327f85108282" } +chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "3e9c2503ae9c5277c2acb74624dc267876dd89b3" } +chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "3e9c2503ae9c5277c2acb74624dc267876dd89b3" } +chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "3e9c2503ae9c5277c2acb74624dc267876dd89b3" } [dev-dependencies] insta = "0.16.0" diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs index 779e7857458a..3e6e1e3331b0 100644 --- a/crates/ra_hir_ty/src/_match.rs +++ b/crates/ra_hir_ty/src/_match.rs @@ -573,14 +573,20 @@ pub(crate) fn is_useful( matrix: &Matrix, v: &PatStack, ) -> MatchCheckResult { - // Handle the special case of enums with no variants. In that case, no match - // arm is useful. - if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) = - cx.infer[cx.match_expr].strip_references() - { - if cx.db.enum_data(*enum_id).variants.is_empty() { + // Handle two special cases: + // - enum with no variants + // - `!` type + // In those cases, no match arm is useful. + match cx.infer[cx.match_expr].strip_references() { + Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) => { + if cx.db.enum_data(*enum_id).variants.is_empty() { + return Ok(Usefulness::NotUseful); + } + } + Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }) => { return Ok(Usefulness::NotUseful); } + _ => (), } if v.is_empty() { @@ -1917,6 +1923,17 @@ mod tests { check_no_diagnostic(content); } + #[test] + fn type_never() { + let content = r" + fn test_fn(never: !) { + match never {} + } + "; + + check_no_diagnostic(content); + } + #[test] fn enum_never_ref() { let content = r" @@ -1929,6 +1946,23 @@ mod tests { check_no_diagnostic(content); } + + #[test] + fn expr_diverges_missing_arm() { + let content = r" + enum Either { + A, + B, + } + fn test_fn() { + match loop {} { + Either::A => (), + } + } + "; + + check_no_diagnostic(content); + } } #[cfg(test)] @@ -1980,26 +2014,6 @@ mod false_negatives { check_no_diagnostic(content); } - #[test] - fn expr_diverges_missing_arm() { - let content = r" - enum Either { - A, - B, - } - fn test_fn() { - match loop {} { - Either::A => (), - } - } - "; - - // This is a false negative. - // Even though the match expression diverges, rustc fails - // to compile here since `Either::B` is missing. - check_no_diagnostic(content); - } - #[test] fn expr_loop_missing_arm() { let content = r" @@ -2018,7 +2032,7 @@ mod false_negatives { // We currently infer the type of `loop { break Foo::A }` to `!`, which // causes us to skip the diagnostic since `Either::A` doesn't type check // with `!`. - check_no_diagnostic(content); + check_diagnostic(content); } #[test] diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index c8fd5486159a..41ac702724e8 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs @@ -131,3 +131,31 @@ impl AstDiagnostic for MissingOkInTailExpr { ast::Expr::cast(node).unwrap() } } + +#[derive(Debug)] +pub struct BreakOutsideOfLoop { + pub file: HirFileId, + pub expr: AstPtr, +} + +impl Diagnostic for BreakOutsideOfLoop { + fn message(&self) -> String { + "break outside of loop".to_string() + } + fn source(&self) -> InFile { + InFile { file_id: self.file, value: self.expr.clone().into() } + } + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +impl AstDiagnostic for BreakOutsideOfLoop { + type AST = ast::Expr; + + fn ast(&self, db: &impl AstDatabase) -> Self::AST { + let root = db.parse_or_expand(self.file).unwrap(); + let node = self.source().value.to_node(&root); + ast::Expr::cast(node).unwrap() + } +} diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs index d03bbd5a7b5c..b9c4d2e89783 100644 --- a/crates/ra_hir_ty/src/display.rs +++ b/crates/ra_hir_ty/src/display.rs @@ -6,28 +6,42 @@ use crate::{ db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate, Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, }; -use hir_def::{generics::TypeParamProvenance, AdtId, AssocContainerId, Lookup}; +use hir_def::{ + find_path, generics::TypeParamProvenance, item_scope::ItemInNs, AdtId, AssocContainerId, + Lookup, ModuleId, +}; use hir_expand::name::Name; -pub struct HirFormatter<'a, 'b> { +pub struct HirFormatter<'a> { pub db: &'a dyn HirDatabase, - fmt: &'a mut fmt::Formatter<'b>, + fmt: &'a mut dyn fmt::Write, buf: String, curr_size: usize, pub(crate) max_size: Option, omit_verbose_types: bool, + display_target: DisplayTarget, } pub trait HirDisplay { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result; + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError>; + /// Returns a `Display`able type that is human-readable. + /// Use this for showing types to the user (e.g. diagnostics) fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self> where Self: Sized, { - HirDisplayWrapper(db, self, None, false) + HirDisplayWrapper { + db, + t: self, + max_size: None, + omit_verbose_types: false, + display_target: DisplayTarget::Diagnostics, + } } + /// Returns a `Display`able type that is human-readable and tries to be succinct. + /// Use this for showing types to the user where space is constrained (e.g. doc popups) fn display_truncated<'a>( &'a self, db: &'a dyn HirDatabase, @@ -36,16 +50,46 @@ pub trait HirDisplay { where Self: Sized, { - HirDisplayWrapper(db, self, max_size, true) + HirDisplayWrapper { + db, + t: self, + max_size, + omit_verbose_types: true, + display_target: DisplayTarget::Diagnostics, + } + } + + /// Returns a String representation of `self` that can be inserted into the given module. + /// Use this when generating code (e.g. assists) + fn display_source_code<'a>( + &'a self, + db: &'a dyn HirDatabase, + module_id: ModuleId, + ) -> Result { + let mut result = String::new(); + match self.hir_fmt(&mut HirFormatter { + db, + fmt: &mut result, + buf: String::with_capacity(20), + curr_size: 0, + max_size: None, + omit_verbose_types: false, + display_target: DisplayTarget::SourceCode { module_id }, + }) { + Ok(()) => {} + Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"), + Err(HirDisplayError::DisplaySourceCodeError(e)) => return Err(e), + }; + Ok(result) } } -impl<'a, 'b> HirFormatter<'a, 'b> { +impl<'a> HirFormatter<'a> { pub fn write_joined( &mut self, iter: impl IntoIterator, sep: &str, - ) -> fmt::Result { + ) -> Result<(), HirDisplayError> { let mut first = true; for e in iter { if !first { @@ -58,14 +102,14 @@ impl<'a, 'b> HirFormatter<'a, 'b> { } /// This allows using the `write!` macro directly with a `HirFormatter`. - pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + pub fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), HirDisplayError> { // We write to a buffer first to track output size self.buf.clear(); fmt::write(&mut self.buf, args)?; self.curr_size += self.buf.len(); // Then we write to the internal formatter from the buffer - self.fmt.write_str(&self.buf) + self.fmt.write_str(&self.buf).map_err(HirDisplayError::from) } pub fn should_truncate(&self) -> bool { @@ -81,34 +125,82 @@ impl<'a, 'b> HirFormatter<'a, 'b> { } } -pub struct HirDisplayWrapper<'a, T>(&'a dyn HirDatabase, &'a T, Option, bool); +#[derive(Clone, Copy)] +enum DisplayTarget { + /// Display types for inlays, doc popups, autocompletion, etc... + /// Showing `{unknown}` or not qualifying paths is fine here. + /// There's no reason for this to fail. + Diagnostics, + /// Display types for inserting them in source files. + /// The generated code should compile, so paths need to be qualified. + SourceCode { module_id: ModuleId }, +} + +impl DisplayTarget { + fn is_source_code(&self) -> bool { + matches!(self, Self::SourceCode {..}) + } +} + +#[derive(Debug)] +pub enum DisplaySourceCodeError { + PathNotFound, +} + +pub enum HirDisplayError { + /// Errors that can occur when generating source code + DisplaySourceCodeError(DisplaySourceCodeError), + /// `FmtError` is required to be compatible with std::fmt::Display + FmtError, +} +impl From for HirDisplayError { + fn from(_: fmt::Error) -> Self { + Self::FmtError + } +} + +pub struct HirDisplayWrapper<'a, T> { + db: &'a dyn HirDatabase, + t: &'a T, + max_size: Option, + omit_verbose_types: bool, + display_target: DisplayTarget, +} impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T> where T: HirDisplay, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.1.hir_fmt(&mut HirFormatter { - db: self.0, + match self.t.hir_fmt(&mut HirFormatter { + db: self.db, fmt: f, buf: String::with_capacity(20), curr_size: 0, - max_size: self.2, - omit_verbose_types: self.3, - }) + max_size: self.max_size, + omit_verbose_types: self.omit_verbose_types, + display_target: self.display_target, + }) { + Ok(()) => Ok(()), + Err(HirDisplayError::FmtError) => Err(fmt::Error), + Err(HirDisplayError::DisplaySourceCodeError(_)) => { + // This should never happen + panic!("HirDisplay failed when calling Display::fmt!") + } + } } } const TYPE_HINT_TRUNCATION: &str = "…"; impl HirDisplay for &Ty { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { HirDisplay::hir_fmt(*self, f) } } impl HirDisplay for ApplicationTy { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{}", TYPE_HINT_TRUNCATION); } @@ -191,45 +283,66 @@ impl HirDisplay for ApplicationTy { } } TypeCtor::Adt(def_id) => { - let name = match def_id { - AdtId::StructId(it) => f.db.struct_data(it).name.clone(), - AdtId::UnionId(it) => f.db.union_data(it).name.clone(), - AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), - }; - write!(f, "{}", name)?; + match f.display_target { + DisplayTarget::Diagnostics => { + let name = match def_id { + AdtId::StructId(it) => f.db.struct_data(it).name.clone(), + AdtId::UnionId(it) => f.db.union_data(it).name.clone(), + AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), + }; + write!(f, "{}", name)?; + } + DisplayTarget::SourceCode { module_id } => { + if let Some(path) = find_path::find_path( + f.db.upcast(), + ItemInNs::Types(def_id.into()), + module_id, + ) { + write!(f, "{}", path)?; + } else { + return Err(HirDisplayError::DisplaySourceCodeError( + DisplaySourceCodeError::PathNotFound, + )); + } + } + } + if self.parameters.len() > 0 { let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); - let parameters_to_write = if f.omit_verbose_types() { - match self - .ctor - .as_generic_def() - .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) - .filter(|defaults| !defaults.is_empty()) - { - None => self.parameters.0.as_ref(), - Some(default_parameters) => { - for (i, parameter) in self.parameters.iter().enumerate() { - match (parameter, default_parameters.get(i)) { - (&Ty::Unknown, _) | (_, None) => { - non_default_parameters.push(parameter.clone()) + let parameters_to_write = + if f.display_target.is_source_code() || f.omit_verbose_types() { + match self + .ctor + .as_generic_def() + .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) + .filter(|defaults| !defaults.is_empty()) + { + None => self.parameters.0.as_ref(), + Some(default_parameters) => { + for (i, parameter) in self.parameters.iter().enumerate() { + match (parameter, default_parameters.get(i)) { + (&Ty::Unknown, _) | (_, None) => { + non_default_parameters.push(parameter.clone()) + } + (_, Some(default_parameter)) + if parameter != default_parameter => + { + non_default_parameters.push(parameter.clone()) + } + _ => (), } - (_, Some(default_parameter)) - if parameter != default_parameter => - { - non_default_parameters.push(parameter.clone()) - } - _ => (), } + &non_default_parameters } - &non_default_parameters } - } - } else { - self.parameters.0.as_ref() - }; - write!(f, "<")?; - f.write_joined(parameters_to_write, ", ")?; - write!(f, ">")?; + } else { + self.parameters.0.as_ref() + }; + if !parameters_to_write.is_empty() { + write!(f, "<")?; + f.write_joined(parameters_to_write, ", ")?; + write!(f, ">")?; + } } } TypeCtor::AssociatedType(type_alias) => { @@ -269,7 +382,7 @@ impl HirDisplay for ApplicationTy { } impl HirDisplay for ProjectionTy { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{}", TYPE_HINT_TRUNCATION); } @@ -287,7 +400,7 @@ impl HirDisplay for ProjectionTy { } impl HirDisplay for Ty { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{}", TYPE_HINT_TRUNCATION); } @@ -332,7 +445,7 @@ impl HirDisplay for Ty { fn write_bounds_like_dyn_trait( predicates: &[GenericPredicate], f: &mut HirFormatter, -) -> fmt::Result { +) -> Result<(), HirDisplayError> { // Note: This code is written to produce nice results (i.e. // corresponding to surface Rust) for types that can occur in // actual Rust. It will have weird results if the predicates @@ -394,7 +507,7 @@ fn write_bounds_like_dyn_trait( } impl TraitRef { - fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result { + fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{}", TYPE_HINT_TRUNCATION); } @@ -416,19 +529,19 @@ impl TraitRef { } impl HirDisplay for TraitRef { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { self.hir_fmt_ext(f, false) } } impl HirDisplay for &GenericPredicate { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { HirDisplay::hir_fmt(*self, f) } } impl HirDisplay for GenericPredicate { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{}", TYPE_HINT_TRUNCATION); } @@ -452,15 +565,15 @@ impl HirDisplay for GenericPredicate { } impl HirDisplay for Obligation { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - match self { - Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)), + fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { + Ok(match self { + Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db))?, Obligation::Projection(proj) => write!( f, "Normalize({} => {})", proj.projection_ty.display(f.db), proj.ty.display(f.db) - ), - } + )?, + }) } } diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index bd4ef69a079a..957d6e0b5792 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs @@ -22,13 +22,14 @@ use rustc_hash::FxHashMap; use hir_def::{ body::Body, - data::{ConstData, FunctionData}, + data::{ConstData, FunctionData, StaticData}, expr::{BindingAnnotation, ExprId, PatId}, lang_item::LangItemTarget, path::{path, Path}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{Mutability, TypeRef}, - AdtId, AssocItemId, DefWithBodyId, FieldId, FunctionId, TraitId, TypeAliasId, VariantId, + AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, TraitId, TypeAliasId, + VariantId, }; use hir_expand::{diagnostics::DiagnosticSink, name::name}; use ra_arena::map::ArenaMap; @@ -71,7 +72,7 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc ctx.collect_const(&db.const_data(c)), DefWithBodyId::FunctionId(f) => ctx.collect_fn(&db.function_data(f)), - DefWithBodyId::StaticId(s) => ctx.collect_const(&db.static_data(s)), + DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)), } ctx.infer_body(); @@ -210,6 +211,14 @@ struct InferenceContext<'a> { /// closures, but currently this is the only field that will change there, /// so it doesn't make sense. return_ty: Ty, + diverges: Diverges, + breakables: Vec, +} + +#[derive(Clone, Debug)] +struct BreakableContext { + pub may_break: bool, + pub break_ty: Ty, } impl<'a> InferenceContext<'a> { @@ -224,6 +233,8 @@ impl<'a> InferenceContext<'a> { owner, body: db.body(owner), resolver, + diverges: Diverges::Maybe, + breakables: Vec::new(), } } @@ -429,43 +440,95 @@ impl<'a> InferenceContext<'a> { let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); // FIXME: this should resolve assoc items as well, see this example: // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 - return match resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path.mod_path()) { - Some(TypeNs::AdtId(AdtId::StructId(strukt))) => { + let (resolution, unresolved) = + match resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { + Some(it) => it, + None => return (Ty::Unknown, None), + }; + return match resolution { + TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = Ty::substs_from_path(&ctx, path, strukt.into()); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.subst(&substs)); - (ty, Some(strukt.into())) + forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } - Some(TypeNs::EnumVariantId(var)) => { + TypeNs::EnumVariantId(var) => { let substs = Ty::substs_from_path(&ctx, path, var.into()); let ty = self.db.ty(var.parent.into()); let ty = self.insert_type_vars(ty.subst(&substs)); - (ty, Some(var.into())) + forbid_unresolved_segments((ty, Some(var.into())), unresolved) } - Some(TypeNs::SelfType(impl_id)) => { + TypeNs::SelfType(impl_id) => { let generics = crate::utils::generics(self.db.upcast(), impl_id.into()); let substs = Substs::type_params_for_generics(&generics); let ty = self.db.impl_self_ty(impl_id).subst(&substs); - let variant = ty_variant(&ty); - (ty, variant) + match unresolved { + None => { + let variant = ty_variant(&ty); + (ty, variant) + } + Some(1) => { + let segment = path.mod_path().segments.last().unwrap(); + // this could be an enum variant or associated type + if let Some((AdtId::EnumId(enum_id), _)) = ty.as_adt() { + let enum_data = self.db.enum_data(enum_id); + if let Some(local_id) = enum_data.variant(segment) { + let variant = EnumVariantId { parent: enum_id, local_id }; + return (ty, Some(variant.into())); + } + } + // FIXME potentially resolve assoc type + (Ty::Unknown, None) + } + Some(_) => { + // FIXME diagnostic + (Ty::Unknown, None) + } + } } - Some(TypeNs::TypeAliasId(it)) => { + TypeNs::TypeAliasId(it) => { let substs = Substs::build_for_def(self.db, it) .fill(std::iter::repeat_with(|| self.table.new_type_var())) .build(); let ty = self.db.ty(it.into()).subst(&substs); let variant = ty_variant(&ty); - (ty, variant) + forbid_unresolved_segments((ty, variant), unresolved) + } + TypeNs::AdtSelfType(_) => { + // FIXME this could happen in array size expressions, once we're checking them + (Ty::Unknown, None) + } + TypeNs::GenericParam(_) => { + // FIXME potentially resolve assoc type + (Ty::Unknown, None) + } + TypeNs::AdtId(AdtId::EnumId(_)) + | TypeNs::AdtId(AdtId::UnionId(_)) + | TypeNs::BuiltinType(_) + | TypeNs::TraitId(_) => { + // FIXME diagnostic + (Ty::Unknown, None) } - Some(_) | None => (Ty::Unknown, None), }; + fn forbid_unresolved_segments( + result: (Ty, Option), + unresolved: Option, + ) -> (Ty, Option) { + if unresolved.is_none() { + result + } else { + // FIXME diagnostic + (Ty::Unknown, None) + } + } + fn ty_variant(ty: &Ty) -> Option { ty.as_adt().and_then(|(adt_id, _)| match adt_id { AdtId::StructId(s) => Some(VariantId::StructId(s)), AdtId::UnionId(u) => Some(VariantId::UnionId(u)), AdtId::EnumId(_) => { - // Error E0071, expected struct, variant or union type, found enum `Foo` + // FIXME Error E0071, expected struct, variant or union type, found enum `Foo` None } }) @@ -476,6 +539,10 @@ impl<'a> InferenceContext<'a> { self.return_ty = self.make_ty(&data.type_ref); } + fn collect_static(&mut self, data: &StaticData) { + self.return_ty = self.make_ty(&data.type_ref); + } + fn collect_fn(&mut self, data: &FunctionData) { let body = Arc::clone(&self.body); // avoid borrow checker problem let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver) @@ -666,15 +733,57 @@ impl Expectation { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum Diverges { + Maybe, + Always, +} + +impl Diverges { + fn is_always(self) -> bool { + self == Diverges::Always + } +} + +impl std::ops::BitAnd for Diverges { + type Output = Self; + fn bitand(self, other: Self) -> Self { + std::cmp::min(self, other) + } +} + +impl std::ops::BitOr for Diverges { + type Output = Self; + fn bitor(self, other: Self) -> Self { + std::cmp::max(self, other) + } +} + +impl std::ops::BitAndAssign for Diverges { + fn bitand_assign(&mut self, other: Self) { + *self = *self & other; + } +} + +impl std::ops::BitOrAssign for Diverges { + fn bitor_assign(&mut self, other: Self) { + *self = *self | other; + } +} + mod diagnostics { use hir_def::{expr::ExprId, FunctionId}; use hir_expand::diagnostics::DiagnosticSink; - use crate::{db::HirDatabase, diagnostics::NoSuchField}; + use crate::{ + db::HirDatabase, + diagnostics::{BreakOutsideOfLoop, NoSuchField}, + }; #[derive(Debug, PartialEq, Eq, Clone)] pub(super) enum InferenceDiagnostic { NoSuchField { expr: ExprId, field: usize }, + BreakOutsideOfLoop { expr: ExprId }, } impl InferenceDiagnostic { @@ -690,6 +799,13 @@ mod diagnostics { let field = source_map.field_syntax(*expr, *field); sink.push(NoSuchField { file: field.file_id, field: field.value }) } + InferenceDiagnostic::BreakOutsideOfLoop { expr } => { + let (_, source_map) = db.body_with_source_map(owner.into()); + let ptr = source_map + .expr_syntax(*expr) + .expect("break outside of loop in synthetic syntax"); + sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value }) + } } } } diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 89200255a287..2ee9adb16425 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs @@ -5,7 +5,7 @@ //! See: https://doc.rust-lang.org/nomicon/coercions.html use hir_def::{lang_item::LangItemTarget, type_ref::Mutability}; -use test_utils::tested_by; +use test_utils::mark; use crate::{autoderef, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor}; @@ -20,21 +20,35 @@ impl<'a> InferenceContext<'a> { self.coerce_inner(from_ty, &to_ty) } - /// Merge two types from different branches, with possible implicit coerce. + /// Merge two types from different branches, with possible coercion. /// - /// Note that it is only possible that one type are coerced to another. - /// Coercing both types to another least upper bound type is not possible in rustc, - /// which will simply result in "incompatible types" error. + /// Mostly this means trying to coerce one to the other, but + /// - if we have two function types for different functions, we need to + /// coerce both to function pointers; + /// - if we were concerned with lifetime subtyping, we'd need to look for a + /// least upper bound. pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty { if self.coerce(ty1, ty2) { ty2.clone() } else if self.coerce(ty2, ty1) { ty1.clone() } else { - tested_by!(coerce_merge_fail_fallback); - // For incompatible types, we use the latter one as result - // to be better recovery for `if` without `else`. - ty2.clone() + if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) { + mark::hit!(coerce_fn_reification); + // Special case: two function types. Try to coerce both to + // pointers to have a chance at getting a match. See + // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 + let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig"); + let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig"); + let ptr_ty1 = Ty::fn_ptr(sig1); + let ptr_ty2 = Ty::fn_ptr(sig2); + self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) + } else { + mark::hit!(coerce_merge_fail_fallback); + // For incompatible types, we use the latter one as result + // to be better recovery for `if` without `else`. + ty2.clone() + } } } @@ -84,9 +98,7 @@ impl<'a> InferenceContext<'a> { match from_ty.callable_sig(self.db) { None => return false, Some(sig) => { - let num_args = sig.params_and_return.len() as u16 - 1; - from_ty = - Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return)); + from_ty = Ty::fn_ptr(sig); } } } diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index efc60986bd03..b28724f0e946 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -1,7 +1,7 @@ //! Type inference for expressions. use std::iter::{repeat, repeat_with}; -use std::sync::Arc; +use std::{mem, sync::Arc}; use hir_def::{ builtin_type::Signedness, @@ -21,11 +21,18 @@ use crate::{ Ty, TypeCtor, Uncertain, }; -use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; +use super::{ + BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, + TypeMismatch, +}; impl<'a> InferenceContext<'a> { pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { let ty = self.infer_expr_inner(tgt_expr, expected); + if ty.is_never() { + // Any expression that produces a value of type `!` must have diverged + self.diverges = Diverges::Always; + } let could_unify = self.unify(&ty, &expected.ty); if !could_unify { self.result.type_mismatches.insert( @@ -64,34 +71,68 @@ impl<'a> InferenceContext<'a> { // if let is desugared to match, so this is always simple if self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); + let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); + let mut both_arms_diverge = Diverges::Always; + let then_ty = self.infer_expr_inner(*then_branch, &expected); + both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); let else_ty = match else_branch { Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), None => Ty::unit(), }; + both_arms_diverge &= self.diverges; + + self.diverges = condition_diverges | both_arms_diverge; self.coerce_merge_branch(&then_ty, &else_ty) } Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), + Expr::TryBlock { body } => { + let _inner = self.infer_expr(*body, expected); + // FIXME should be std::result::Result<{inner}, _> + Ty::Unknown + } Expr::Loop { body } => { + self.breakables.push(BreakableContext { + may_break: false, + break_ty: self.table.new_type_var(), + }); self.infer_expr(*body, &Expectation::has_type(Ty::unit())); - // FIXME handle break with value - Ty::simple(TypeCtor::Never) + + let ctxt = self.breakables.pop().expect("breakable stack broken"); + if ctxt.may_break { + self.diverges = Diverges::Maybe; + } + + if ctxt.may_break { + ctxt.break_ty + } else { + Ty::simple(TypeCtor::Never) + } } Expr::While { condition, body } => { + self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); // while let is desugared to a match loop, so this is always simple while self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); self.infer_expr(*body, &Expectation::has_type(Ty::unit())); + let _ctxt = self.breakables.pop().expect("breakable stack broken"); + // the body may not run, so it diverging doesn't mean we diverge + self.diverges = Diverges::Maybe; Ty::unit() } Expr::For { iterable, body, pat } => { let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); + self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); let pat_ty = self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); self.infer_pat(*pat, &pat_ty, BindingMode::default()); + self.infer_expr(*body, &Expectation::has_type(Ty::unit())); + let _ctxt = self.breakables.pop().expect("breakable stack broken"); + // the body may not run, so it diverging doesn't mean we diverge + self.diverges = Diverges::Maybe; Ty::unit() } Expr::Lambda { body, args, ret_type, arg_types } => { @@ -127,10 +168,12 @@ impl<'a> InferenceContext<'a> { // infer the body. self.coerce(&closure_ty, &expected.ty); - let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone()); + let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); + let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); + self.diverges = prev_diverges; self.return_ty = prev_ret_ty; closure_ty @@ -160,7 +203,11 @@ impl<'a> InferenceContext<'a> { self.table.new_type_var() }; + let matchee_diverges = self.diverges; + let mut all_arms_diverge = Diverges::Always; + for arm in arms { + self.diverges = Diverges::Maybe; let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); if let Some(guard_expr) = arm.guard { self.infer_expr( @@ -170,9 +217,12 @@ impl<'a> InferenceContext<'a> { } let arm_ty = self.infer_expr_inner(arm.expr, &expected); + all_arms_diverge &= self.diverges; result_ty = self.coerce_merge_branch(&result_ty, &arm_ty); } + self.diverges = matchee_diverges | all_arms_diverge; + result_ty } Expr::Path(p) => { @@ -182,10 +232,29 @@ impl<'a> InferenceContext<'a> { } Expr::Continue => Ty::simple(TypeCtor::Never), Expr::Break { expr } => { - if let Some(expr) = expr { - // FIXME handle break with value - self.infer_expr(*expr, &Expectation::none()); + let val_ty = if let Some(expr) = expr { + self.infer_expr(*expr, &Expectation::none()) + } else { + Ty::unit() + }; + + let last_ty = if let Some(ctxt) = self.breakables.last() { + ctxt.break_ty.clone() + } else { + Ty::Unknown + }; + + let merged_type = self.coerce_merge_branch(&last_ty, &val_ty); + + if let Some(ctxt) = self.breakables.last_mut() { + ctxt.break_ty = merged_type; + ctxt.may_break = true; + } else { + self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { + expr: tgt_expr, + }); } + Ty::simple(TypeCtor::Never) } Expr::Return { expr } => { @@ -496,8 +565,8 @@ impl<'a> InferenceContext<'a> { } Literal::ByteString(..) => { let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); - let slice_type = Ty::apply_one(TypeCtor::Slice, byte_type); - Ty::apply_one(TypeCtor::Ref(Mutability::Shared), slice_type) + let array_type = Ty::apply_one(TypeCtor::Array, byte_type); + Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type) } Literal::Char(..) => Ty::simple(TypeCtor::Char), Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), @@ -517,7 +586,6 @@ impl<'a> InferenceContext<'a> { tail: Option, expected: &Expectation, ) -> Ty { - let mut diverges = false; for stmt in statements { match stmt { Statement::Let { pat, type_ref, initializer } => { @@ -539,9 +607,7 @@ impl<'a> InferenceContext<'a> { self.infer_pat(*pat, &ty, BindingMode::default()); } Statement::Expr(expr) => { - if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) { - diverges = true; - } + self.infer_expr(*expr, &Expectation::none()); } } } @@ -549,14 +615,22 @@ impl<'a> InferenceContext<'a> { let ty = if let Some(expr) = tail { self.infer_expr_coerce(expr, expected) } else { - self.coerce(&Ty::unit(), expected.coercion_target()); - Ty::unit() + // Citing rustc: if there is no explicit tail expression, + // that is typically equivalent to a tail expression + // of `()` -- except if the block diverges. In that + // case, there is no value supplied from the tail + // expression (assuming there are no other breaks, + // this implies that the type of the block will be + // `!`). + if self.diverges.is_always() { + // we don't even make an attempt at coercion + self.table.new_maybe_never_type_var() + } else { + self.coerce(&Ty::unit(), expected.coercion_target()); + Ty::unit() + } }; - if diverges { - Ty::simple(TypeCtor::Never) - } else { - ty - } + ty } fn infer_method_call( diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs index 54ec870dfc8a..4006f595d18e 100644 --- a/crates/ra_hir_ty/src/infer/pat.rs +++ b/crates/ra_hir_ty/src/infer/pat.rs @@ -10,7 +10,7 @@ use hir_def::{ FieldId, }; use hir_expand::name::Name; -use test_utils::tested_by; +use test_utils::mark; use super::{BindingMode, Expectation, InferenceContext}; use crate::{utils::variant_data, Substs, Ty, TypeCtor}; @@ -111,7 +111,7 @@ impl<'a> InferenceContext<'a> { } } } else if let Pat::Ref { .. } = &body[pat] { - tested_by!(match_ergonomics_ref); + mark::hit!(match_ergonomics_ref); // When you encounter a `&pat` pattern, reset to Move. // This is so that `w` is by value: `let (_, &w) = &(1, &2);` default_bm = BindingMode::Move; diff --git a/crates/ra_hir_ty/src/infer/path.rs b/crates/ra_hir_ty/src/infer/path.rs index 2b6bc0f798f7..1c2e56fb0d82 100644 --- a/crates/ra_hir_ty/src/infer/path.rs +++ b/crates/ra_hir_ty/src/infer/path.rs @@ -5,7 +5,7 @@ use std::iter; use hir_def::{ path::{Path, PathSegment}, resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs}, - AssocContainerId, AssocItemId, Lookup, + AdtId, AssocContainerId, AssocItemId, EnumVariantId, Lookup, }; use hir_expand::name::Name; @@ -77,6 +77,18 @@ impl<'a> InferenceContext<'a> { it.into() } + ValueNs::ImplSelf(impl_id) => { + let generics = crate::utils::generics(self.db.upcast(), impl_id.into()); + let substs = Substs::type_params_for_generics(&generics); + let ty = self.db.impl_self_ty(impl_id).subst(&substs); + if let Some((AdtId::StructId(struct_id), _)) = ty.as_adt() { + let ty = self.db.value_ty(struct_id.into()).subst(&substs); + return Some(ty); + } else { + // FIXME: diagnostic, invalid Self reference + return None; + } + } }; let ty = self.db.value_ty(typable); @@ -199,6 +211,10 @@ impl<'a> InferenceContext<'a> { return None; } + if let Some(result) = self.resolve_enum_variant_on_ty(&ty, name, id) { + return Some(result); + } + let canonical_ty = self.canonicalizer().canonicalize_ty(ty.clone()); let krate = self.resolver.krate()?; let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); @@ -250,4 +266,21 @@ impl<'a> InferenceContext<'a> { }, ) } + + fn resolve_enum_variant_on_ty( + &mut self, + ty: &Ty, + name: &Name, + id: ExprOrPatId, + ) -> Option<(ValueNs, Option)> { + let (enum_id, subst) = match ty.as_adt() { + Some((AdtId::EnumId(e), subst)) => (e, subst), + _ => return None, + }; + let enum_data = self.db.enum_data(enum_id); + let local_id = enum_data.variant(name)?; + let variant = EnumVariantId { parent: enum_id, local_id }; + self.write_variant_resolution(id, variant.into()); + Some((ValueNs::EnumVariantId(variant), Some(subst.clone()))) + } } diff --git a/crates/ra_hir_ty/src/infer/unify.rs b/crates/ra_hir_ty/src/infer/unify.rs index ab0bc8b70b65..269495ca0bd2 100644 --- a/crates/ra_hir_ty/src/infer/unify.rs +++ b/crates/ra_hir_ty/src/infer/unify.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; -use test_utils::tested_by; +use test_utils::mark; use super::{InferenceContext, Obligation}; use crate::{ @@ -313,7 +313,7 @@ impl InferenceTable { // more than once for i in 0..3 { if i > 0 { - tested_by!(type_var_resolves_to_int_var); + mark::hit!(type_var_resolves_to_int_var); } match &*ty { Ty::Infer(tv) => { @@ -342,7 +342,7 @@ impl InferenceTable { Ty::Infer(tv) => { let inner = tv.to_inner(); if tv_stack.contains(&inner) { - tested_by!(type_var_cycles_resolve_as_possible); + mark::hit!(type_var_cycles_resolve_as_possible); // recursive type return tv.fallback_value(); } @@ -369,7 +369,7 @@ impl InferenceTable { Ty::Infer(tv) => { let inner = tv.to_inner(); if tv_stack.contains(&inner) { - tested_by!(type_var_cycles_resolve_completely); + mark::hit!(type_var_cycles_resolve_completely); // recursive type return tv.fallback_value(); } diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index a6f56c661e2a..c87ee06ce88c 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -42,7 +42,6 @@ pub mod expr; mod tests; #[cfg(test)] mod test_db; -mod marks; mod _match; use std::ops::Deref; @@ -427,6 +426,11 @@ impl Substs { } } +/// Return an index of a parameter in the generic type parameter list by it's id. +pub fn param_idx(db: &dyn HirDatabase, id: TypeParamId) -> Option { + generics(db.upcast(), id.parent).param_idx(id) +} + #[derive(Debug, Clone)] pub struct SubstsBuilder { vec: Vec, @@ -683,6 +687,12 @@ impl Ty { pub fn unit() -> Self { Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) } + pub fn fn_ptr(sig: FnSig) -> Self { + Ty::apply( + TypeCtor::FnPtr { num_args: sig.params().len() as u16 }, + Substs(sig.params_and_return), + ) + } pub fn as_reference(&self) -> Option<(&Ty, Mutability)> { match self { @@ -730,6 +740,10 @@ impl Ty { } } + pub fn is_never(&self) -> bool { + matches!(self, Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. })) + } + /// If this is a `dyn Trait` type, this returns the `Trait` part. pub fn dyn_trait_ref(&self) -> Option<&TraitRef> { match self { @@ -793,15 +807,13 @@ impl Ty { } } - /// If this is an `impl Trait` or `dyn Trait`, returns that trait. - pub fn inherent_trait(&self) -> Option { + /// If this is a `dyn Trait`, returns that trait. + pub fn dyn_trait(&self) -> Option { match self { - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { - predicates.iter().find_map(|pred| match pred { - GenericPredicate::Implemented(tr) => Some(tr.trait_), - _ => None, - }) - } + Ty::Dyn(predicates) => predicates.iter().find_map(|pred| match pred { + GenericPredicate::Implemented(tr) => Some(tr.trait_), + _ => None, + }), _ => None, } } diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index 9ad6dbe075a2..35ac86a461eb 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs @@ -812,7 +812,7 @@ impl TraitEnvironment { // add `Self: Trait` to the environment in trait // function default implementations (and hypothetical code // inside consts or type aliases) - test_utils::tested_by!(trait_self_implements_self); + test_utils::mark::hit!(trait_self_implements_self); let substs = Substs::type_params(db, trait_id); let trait_ref = TraitRef { trait_: trait_id, substs }; let pred = GenericPredicate::Implemented(trait_ref); diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs deleted file mode 100644 index de5cb1d6bcb8..000000000000 --- a/crates/ra_hir_ty/src/marks.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! See test_utils/src/marks.rs - -test_utils::marks!( - type_var_cycles_resolve_completely - type_var_cycles_resolve_as_possible - type_var_resolves_to_int_var - impl_self_type_match_without_receiver - match_ergonomics_ref - coerce_merge_fail_fallback - trait_self_implements_self -); diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs index 657284fd018a..e19628fdf728 100644 --- a/crates/ra_hir_ty/src/method_resolution.rs +++ b/crates/ra_hir_ty/src/method_resolution.rs @@ -408,8 +408,9 @@ fn iterate_trait_method_candidates( receiver_ty: Option<&Canonical>, mut callback: impl FnMut(&Ty, AssocItemId) -> Option, ) -> Option { - // if ty is `impl Trait` or `dyn Trait`, the trait doesn't need to be in scope - let inherent_trait = self_ty.value.inherent_trait().into_iter(); + // if ty is `dyn Trait`, the trait doesn't need to be in scope + let inherent_trait = + self_ty.value.dyn_trait().into_iter().flat_map(|t| all_super_traits(db.upcast(), t)); let env_traits = if let Ty::Placeholder(_) = self_ty.value { // if we have `T: Trait` in the param env, the trait doesn't need to be in scope env.trait_predicates_for_self_ty(&self_ty.value) @@ -468,7 +469,7 @@ fn iterate_inherent_methods( // already happens in `is_valid_candidate` above; if not, we // check it here if receiver_ty.is_none() && inherent_impl_substs(db, impl_def, self_ty).is_none() { - test_utils::tested_by!(impl_self_type_match_without_receiver); + test_utils::mark::hit!(impl_self_type_match_without_receiver); continue; } if let Some(result) = callback(&self_ty.value, item) { @@ -601,11 +602,6 @@ pub fn implements_trait( krate: CrateId, trait_: TraitId, ) -> bool { - if ty.value.inherent_trait() == Some(trait_) { - // FIXME this is a bit of a hack, since Chalk should say the same thing - // anyway, but currently Chalk doesn't implement `dyn/impl Trait` yet - return true; - } let goal = generic_implements_goal(db, env, trait_, ty.clone()); let solution = db.trait_solve(krate, goal); diff --git a/crates/ra_hir_ty/src/op.rs b/crates/ra_hir_ty/src/op.rs index 54e2bd05a196..0870874fce6c 100644 --- a/crates/ra_hir_ty/src/op.rs +++ b/crates/ra_hir_ty/src/op.rs @@ -30,7 +30,8 @@ pub(super) fn binary_op_return_ty(op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Ty { pub(super) fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty { match op { BinaryOp::LogicOp(..) => Ty::simple(TypeCtor::Bool), - BinaryOp::Assignment { op: None } | BinaryOp::CmpOp(CmpOp::Eq { .. }) => match lhs_ty { + BinaryOp::Assignment { op: None } => lhs_ty, + BinaryOp::CmpOp(CmpOp::Eq { .. }) => match lhs_ty { Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { TypeCtor::Int(..) | TypeCtor::Float(..) diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index 588d812820a8..1fe05c70c961 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs @@ -6,6 +6,7 @@ mod patterns; mod traits; mod method_resolution; mod macros; +mod display_source_code; use std::sync::Arc; @@ -16,7 +17,7 @@ use hir_def::{ item_scope::ItemScope, keys, nameres::CrateDefMap, - AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, + AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, ModuleId, }; use hir_expand::{db::AstDatabase, InFile}; use insta::assert_snapshot; @@ -37,6 +38,18 @@ use crate::{ // update the snapshots. fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { + type_at_pos_displayed(db, pos, |ty, _| ty.display(db).to_string()) +} + +fn displayed_source_at_pos(db: &TestDB, pos: FilePosition) -> String { + type_at_pos_displayed(db, pos, |ty, module_id| ty.display_source_code(db, module_id).unwrap()) +} + +fn type_at_pos_displayed( + db: &TestDB, + pos: FilePosition, + display_fn: impl FnOnce(&Ty, ModuleId) -> String, +) -> String { let file = db.parse(pos.file_id).ok().unwrap(); let expr = algo::find_node_at_offset::(file.syntax(), pos.offset).unwrap(); let fn_def = expr.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); @@ -49,7 +62,7 @@ fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) { let infer = db.infer(func.into()); let ty = &infer[expr_id]; - return ty.display(db).to_string(); + return display_fn(ty, module); } panic!("Can't find expression") } @@ -360,6 +373,33 @@ fn no_such_field_with_feature_flag_diagnostics() { assert_snapshot!(diagnostics, @r###""###); } +#[test] +fn no_such_field_enum_with_feature_flag_diagnostics() { + let diagnostics = TestDB::with_files( + r#" + //- /lib.rs crate:foo cfg:feature=foo + enum Foo { + #[cfg(not(feature = "foo"))] + Buz, + #[cfg(feature = "foo")] + Bar, + Baz + } + + fn test_fn(f: Foo) { + match f { + Foo::Bar => {}, + Foo::Baz => {}, + } + } + "#, + ) + .diagnostics() + .0; + + assert_snapshot!(diagnostics, @r###""###); +} + #[test] fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() { let diagnostics = TestDB::with_files( @@ -491,3 +531,21 @@ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { assert_snapshot!(diagnostics, @""); } + +#[test] +fn break_outside_of_loop() { + let diagnostics = TestDB::with_files( + r" + //- /lib.rs + fn foo() { + break; + } + ", + ) + .diagnostics() + .0; + + assert_snapshot!(diagnostics, @r###""break": break outside of loop + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs index e6fb3e1231f6..2cc4f4bf964c 100644 --- a/crates/ra_hir_ty/src/tests/coercion.rs +++ b/crates/ra_hir_ty/src/tests/coercion.rs @@ -1,6 +1,6 @@ use super::infer_with_mismatches; use insta::assert_snapshot; -use test_utils::covers; +use test_utils::mark; // Infer with some common definitions and impls. fn infer(source: &str) -> String { @@ -339,7 +339,7 @@ fn test(i: i32) { #[test] fn coerce_merge_one_by_one1() { - covers!(coerce_merge_fail_fallback); + mark::check!(coerce_merge_fail_fallback); assert_snapshot!( infer(r#" @@ -384,7 +384,7 @@ fn foo() -> u32 { } "#, true), @r###" - 17..40 '{ ...own; }': ! + 17..40 '{ ...own; }': u32 23..37 'return unknown': ! 30..37 'unknown': u32 "### @@ -514,7 +514,7 @@ fn foo() { 27..103 '{ ... }': &u32 37..82 'if tru... }': () 40..44 'true': bool - 45..82 '{ ... }': ! + 45..82 '{ ... }': () 59..71 'return &1u32': ! 66..71 '&1u32': &u32 67..71 '1u32': u32 @@ -545,6 +545,48 @@ fn test() { ); } +#[test] +fn coerce_fn_items_in_match_arms() { + mark::check!(coerce_fn_reification); + assert_snapshot!( + infer_with_mismatches(r#" +fn foo1(x: u32) -> isize { 1 } +fn foo2(x: u32) -> isize { 2 } +fn foo3(x: u32) -> isize { 3 } +fn test() { + let x = match 1 { + 1 => foo1, + 2 => foo2, + _ => foo3, + }; +} +"#, true), + @r###" + 9..10 'x': u32 + 26..31 '{ 1 }': isize + 28..29 '1': isize + 40..41 'x': u32 + 57..62 '{ 2 }': isize + 59..60 '2': isize + 71..72 'x': u32 + 88..93 '{ 3 }': isize + 90..91 '3': isize + 104..193 '{ ... }; }': () + 114..115 'x': fn(u32) -> isize + 118..190 'match ... }': fn(u32) -> isize + 124..125 '1': i32 + 136..137 '1': i32 + 136..137 '1': i32 + 141..145 'foo1': fn foo1(u32) -> isize + 155..156 '2': i32 + 155..156 '2': i32 + 160..164 'foo2': fn foo2(u32) -> isize + 174..175 '_': i32 + 179..183 'foo3': fn foo3(u32) -> isize + "### + ); +} + #[test] fn coerce_closure_to_fn_ptr() { assert_snapshot!( diff --git a/crates/ra_hir_ty/src/tests/display_source_code.rs b/crates/ra_hir_ty/src/tests/display_source_code.rs new file mode 100644 index 000000000000..4088b1d22d94 --- /dev/null +++ b/crates/ra_hir_ty/src/tests/display_source_code.rs @@ -0,0 +1,50 @@ +use super::displayed_source_at_pos; +use crate::test_db::TestDB; +use ra_db::fixture::WithFixture; + +#[test] +fn qualify_path_to_submodule() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs + +mod foo { + pub struct Foo; +} + +fn bar() { + let foo: foo::Foo = foo::Foo; + foo<|> +} + +"#, + ); + assert_eq!("foo::Foo", displayed_source_at_pos(&db, pos)); +} + +#[test] +fn omit_default_type_parameters() { + let (db, pos) = TestDB::with_position( + r" + //- /main.rs + struct Foo { t: T } + fn main() { + let foo = Foo { t: 5 }; + foo<|>; + } + ", + ); + assert_eq!("Foo", displayed_source_at_pos(&db, pos)); + + let (db, pos) = TestDB::with_position( + r" + //- /main.rs + struct Foo { k: K, t: T } + fn main() { + let foo = Foo { k: 400, t: 5 }; + foo<|>; + } + ", + ); + assert_eq!("Foo", displayed_source_at_pos(&db, pos)); +} diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs index 1f796876da2c..4c6099aa230d 100644 --- a/crates/ra_hir_ty/src/tests/macros.rs +++ b/crates/ra_hir_ty/src/tests/macros.rs @@ -197,7 +197,7 @@ fn spam() { !0..6 '1isize': isize !0..6 '1isize': isize !0..6 '1isize': isize - 54..457 '{ ...!(); }': ! + 54..457 '{ ...!(); }': () 88..109 'spam!(...am!())': {unknown} 115..134 'for _ ...!() {}': () 119..120 '_': {unknown} @@ -269,7 +269,7 @@ fn test() { S.foo()<|>; } } #[test] -fn infer_impl_items_generated_by_macros() { +fn infer_assoc_items_generated_by_macros() { let t = type_at( r#" //- /main.rs @@ -288,7 +288,7 @@ fn test() { S.foo()<|>; } } #[test] -fn infer_impl_items_generated_by_macros_chain() { +fn infer_assoc_items_generated_by_macros_chain() { let t = type_at( r#" //- /main.rs @@ -427,6 +427,32 @@ fn main() { ); } +#[test] +fn infer_local_inner_macros() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:foo +fn test() { + let x = foo::foo!(1); + x<|>; +} + +//- /lib.rs crate:foo +#[macro_export(local_inner_macros)] +macro_rules! foo { + (1) => { bar!() }; +} + +#[macro_export] +macro_rules! bar { + () => { 42 } +} + +"#, + ); + assert_eq!("i32", type_at_pos(&db, pos)); +} + #[test] fn infer_builtin_macros_line() { assert_snapshot!( diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs index ab87f598a62b..558a70022dce 100644 --- a/crates/ra_hir_ty/src/tests/method_resolution.rs +++ b/crates/ra_hir_ty/src/tests/method_resolution.rs @@ -17,8 +17,8 @@ impl [T] { #[lang = "slice_alloc"] impl [T] {} -fn test() { - <[_]>::foo(b"foo"); +fn test(x: &[u8]) { + <[_]>::foo(x); } "#), @r###" @@ -26,10 +26,11 @@ fn test() { 56..79 '{ ... }': T 66..73 'loop {}': ! 71..73 '{}': () - 133..160 '{ ...o"); }': () - 139..149 '<[_]>::foo': fn foo(&[u8]) -> u8 - 139..157 '<[_]>:..."foo")': u8 - 150..156 'b"foo"': &[u8] + 131..132 'x': &[u8] + 141..163 '{ ...(x); }': () + 147..157 '<[_]>::foo': fn foo(&[u8]) -> u8 + 147..160 '<[_]>::foo(x)': u8 + 158..159 'x': &[u8] "### ); } @@ -983,7 +984,7 @@ fn test() { S2.into()<|>; } #[test] fn method_resolution_overloaded_method() { - test_utils::covers!(impl_self_type_match_without_receiver); + test_utils::mark::check!(impl_self_type_match_without_receiver); let t = type_at( r#" //- main.rs @@ -1095,3 +1096,34 @@ fn test() { (S {}).method()<|>; } ); assert_eq!(t, "()"); } + +#[test] +fn dyn_trait_super_trait_not_in_scope() { + assert_snapshot!( + infer(r#" +mod m { + pub trait SuperTrait { + fn foo(&self) -> u32 { 0 } + } +} +trait Trait: m::SuperTrait {} + +struct S; +impl m::SuperTrait for S {} +impl Trait for S {} + +fn test(d: &dyn Trait) { + d.foo(); +} +"#), + @r###" + 52..56 'self': &Self + 65..70 '{ 0 }': u32 + 67..68 '0': u32 + 177..178 'd': &dyn Trait + 192..208 '{ ...o(); }': () + 198..199 'd': &dyn Trait + 198..205 'd.foo()': u32 + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/never_type.rs b/crates/ra_hir_ty/src/tests/never_type.rs index a772094804a1..082c47208848 100644 --- a/crates/ra_hir_ty/src/tests/never_type.rs +++ b/crates/ra_hir_ty/src/tests/never_type.rs @@ -1,4 +1,6 @@ -use super::type_at; +use insta::assert_snapshot; + +use super::{infer_with_mismatches, type_at}; #[test] fn infer_never1() { @@ -261,3 +263,176 @@ fn test(a: i32) { ); assert_eq!(t, "f64"); } + +#[test] +fn diverging_expression_1() { + let t = infer_with_mismatches( + r#" +//- /main.rs +fn test1() { + let x: u32 = return; +} +fn test2() { + let x: u32 = { return; }; +} +fn test3() { + let x: u32 = loop {}; +} +fn test4() { + let x: u32 = { loop {} }; +} +fn test5() { + let x: u32 = { if true { loop {}; } else { loop {}; } }; +} +fn test6() { + let x: u32 = { let y: u32 = { loop {}; }; }; +} +"#, + true, + ); + assert_snapshot!(t, @r###" + 25..53 '{ ...urn; }': () + 35..36 'x': u32 + 44..50 'return': ! + 65..98 '{ ...; }; }': () + 75..76 'x': u32 + 84..95 '{ return; }': u32 + 86..92 'return': ! + 110..139 '{ ... {}; }': () + 120..121 'x': u32 + 129..136 'loop {}': ! + 134..136 '{}': () + 151..184 '{ ...} }; }': () + 161..162 'x': u32 + 170..181 '{ loop {} }': u32 + 172..179 'loop {}': ! + 177..179 '{}': () + 196..260 '{ ...} }; }': () + 206..207 'x': u32 + 215..257 '{ if t...}; } }': u32 + 217..255 'if tru... {}; }': u32 + 220..224 'true': bool + 225..237 '{ loop {}; }': u32 + 227..234 'loop {}': ! + 232..234 '{}': () + 243..255 '{ loop {}; }': u32 + 245..252 'loop {}': ! + 250..252 '{}': () + 272..324 '{ ...; }; }': () + 282..283 'x': u32 + 291..321 '{ let ...; }; }': u32 + 297..298 'y': u32 + 306..318 '{ loop {}; }': u32 + 308..315 'loop {}': ! + 313..315 '{}': () + "###); +} + +#[test] +fn diverging_expression_2() { + let t = infer_with_mismatches( + r#" +//- /main.rs +fn test1() { + // should give type mismatch + let x: u32 = { loop {}; "foo" }; +} +"#, + true, + ); + assert_snapshot!(t, @r###" + 25..98 '{ ..." }; }': () + 68..69 'x': u32 + 77..95 '{ loop...foo" }': &str + 79..86 'loop {}': ! + 84..86 '{}': () + 88..93 '"foo"': &str + 77..95: expected u32, got &str + 88..93: expected u32, got &str + "###); +} + +#[test] +fn diverging_expression_3_break() { + let t = infer_with_mismatches( + r#" +//- /main.rs +fn test1() { + // should give type mismatch + let x: u32 = { loop { break; } }; +} +fn test2() { + // should give type mismatch + let x: u32 = { for a in b { break; }; }; + // should give type mismatch as well + let x: u32 = { for a in b {}; }; + // should give type mismatch as well + let x: u32 = { for a in b { return; }; }; +} +fn test3() { + // should give type mismatch + let x: u32 = { while true { break; }; }; + // should give type mismatch as well -- there's an implicit break, even if it's never hit + let x: u32 = { while true {}; }; + // should give type mismatch as well + let x: u32 = { while true { return; }; }; +} +"#, + true, + ); + assert_snapshot!(t, @r###" + 25..99 '{ ...} }; }': () + 68..69 'x': u32 + 77..96 '{ loop...k; } }': () + 79..94 'loop { break; }': () + 84..94 '{ break; }': () + 86..91 'break': ! + 77..96: expected u32, got () + 79..94: expected u32, got () + 111..357 '{ ...; }; }': () + 154..155 'x': u32 + 163..189 '{ for ...; }; }': () + 165..186 'for a ...eak; }': () + 169..170 'a': {unknown} + 174..175 'b': {unknown} + 176..186 '{ break; }': () + 178..183 'break': ! + 240..241 'x': u32 + 249..267 '{ for ... {}; }': () + 251..264 'for a in b {}': () + 255..256 'a': {unknown} + 260..261 'b': {unknown} + 262..264 '{}': () + 318..319 'x': u32 + 327..354 '{ for ...; }; }': () + 329..351 'for a ...urn; }': () + 333..334 'a': {unknown} + 338..339 'b': {unknown} + 340..351 '{ return; }': () + 342..348 'return': ! + 163..189: expected u32, got () + 249..267: expected u32, got () + 327..354: expected u32, got () + 369..668 '{ ...; }; }': () + 412..413 'x': u32 + 421..447 '{ whil...; }; }': () + 423..444 'while ...eak; }': () + 429..433 'true': bool + 434..444 '{ break; }': () + 436..441 'break': ! + 551..552 'x': u32 + 560..578 '{ whil... {}; }': () + 562..575 'while true {}': () + 568..572 'true': bool + 573..575 '{}': () + 629..630 'x': u32 + 638..665 '{ whil...; }; }': () + 640..662 'while ...urn; }': () + 646..650 'true': bool + 651..662 '{ return; }': () + 653..659 'return': ! + 421..447: expected u32, got () + 560..578: expected u32, got () + 638..665: expected u32, got () + "###); +} diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs index af291092d30c..0c5f972a2c6e 100644 --- a/crates/ra_hir_ty/src/tests/patterns.rs +++ b/crates/ra_hir_ty/src/tests/patterns.rs @@ -1,5 +1,5 @@ use insta::assert_snapshot; -use test_utils::covers; +use test_utils::mark; use super::{infer, infer_with_mismatches}; @@ -197,7 +197,7 @@ fn test() { #[test] fn infer_pattern_match_ergonomics_ref() { - covers!(match_ergonomics_ref); + mark::check!(match_ergonomics_ref); assert_snapshot!( infer(r#" fn test() { @@ -368,6 +368,45 @@ fn test() { ); } +#[test] +fn enum_variant_through_self_in_pattern() { + assert_snapshot!( + infer(r#" +enum E { + A { x: usize }, + B(usize), + C +} + +impl E { + fn test() { + match (loop {}) { + Self::A { x } => { x; }, + Self::B(x) => { x; }, + Self::C => {}, + }; + } +} +"#), + @r###" + 76..218 '{ ... }': () + 86..211 'match ... }': () + 93..100 'loop {}': ! + 98..100 '{}': () + 116..129 'Self::A { x }': E + 126..127 'x': usize + 133..139 '{ x; }': () + 135..136 'x': usize + 153..163 'Self::B(x)': E + 161..162 'x': usize + 167..173 '{ x; }': () + 169..170 'x': usize + 187..194 'Self::C': E + 198..200 '{}': () + "### + ); +} + #[test] fn infer_generics_in_patterns() { assert_snapshot!( diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs index 8a1292c7aa0e..1f004bd63014 100644 --- a/crates/ra_hir_ty/src/tests/regression.rs +++ b/crates/ra_hir_ty/src/tests/regression.rs @@ -1,9 +1,10 @@ use insta::assert_snapshot; -use test_utils::covers; +use ra_db::fixture::WithFixture; +use test_utils::mark; + +use crate::test_db::TestDB; use super::infer; -use crate::test_db::TestDB; -use ra_db::fixture::WithFixture; #[test] fn bug_484() { @@ -89,8 +90,8 @@ fn quux() { #[test] fn recursive_vars() { - covers!(type_var_cycles_resolve_completely); - covers!(type_var_cycles_resolve_as_possible); + mark::check!(type_var_cycles_resolve_completely); + mark::check!(type_var_cycles_resolve_as_possible); assert_snapshot!( infer(r#" fn test() { @@ -112,8 +113,6 @@ fn test() { #[test] fn recursive_vars_2() { - covers!(type_var_cycles_resolve_completely); - covers!(type_var_cycles_resolve_as_possible); assert_snapshot!( infer(r#" fn test() { @@ -170,7 +169,7 @@ fn write() { #[test] fn infer_std_crash_2() { - covers!(type_var_resolves_to_int_var); + mark::check!(type_var_resolves_to_int_var); // caused "equating two type variables, ...", taken from std assert_snapshot!( infer(r#" @@ -534,6 +533,66 @@ fn foo(b: Bar) { ); } +#[test] +fn issue_4235_name_conflicts() { + assert_snapshot!( + infer(r#" +struct FOO {} +static FOO:FOO = FOO {}; + +impl FOO { + fn foo(&self) {} +} + +fn main() { + let a = &FOO; + a.foo(); +} +"#), @r###" + 32..38 'FOO {}': FOO + 64..68 'self': &FOO + 70..72 '{}': () + 86..120 '{ ...o(); }': () + 96..97 'a': &FOO + 100..104 '&FOO': &FOO + 101..104 'FOO': FOO + 110..111 'a': &FOO + 110..117 'a.foo()': () +"### + ); +} + +#[test] +fn issue_4465_dollar_crate_at_type() { + assert_snapshot!( + infer(r#" +pub struct Foo {} +pub fn anything() -> T { + loop {} +} +macro_rules! foo { + () => {{ + let r: $crate::Foo = anything(); + r + }}; +} +fn main() { + let _a = foo!(); +} +"#), @r###" + 45..60 '{ loop {} }': T + 51..58 'loop {}': ! + 56..58 '{}': () + !0..31 '{letr:...g();r}': Foo + !4..5 'r': Foo + !18..26 'anything': fn anything() -> Foo + !18..28 'anything()': Foo + !29..30 'r': Foo + 164..188 '{ ...!(); }': () + 174..176 '_a': Foo +"###); +} + #[test] fn issue_4053_diesel_where_clauses() { assert_snapshot!( diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 56abc65b8c65..fd2208af280e 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs @@ -179,7 +179,7 @@ fn test(a: u32, b: isize, c: !, d: &str) { 17..18 'b': isize 27..28 'c': ! 33..34 'd': &str - 42..121 '{ ...f32; }': ! + 42..121 '{ ...f32; }': () 48..49 'a': u32 55..56 'b': isize 62..63 'c': ! @@ -414,7 +414,7 @@ fn test() { 27..31 '5f32': f32 37..41 '5f64': f64 47..54 '"hello"': &str - 60..68 'b"bytes"': &[u8] + 60..68 'b"bytes"': &[u8; _] 74..77 ''c'': char 83..87 'b'b'': u8 93..97 '3.14': f64 @@ -422,7 +422,7 @@ fn test() { 113..118 'false': bool 124..128 'true': bool 134..202 'r#" ... "#': &str - 208..218 'br#"yolo"#': &[u8] + 208..218 'br#"yolo"#': &[u8; _] "### ); } @@ -575,6 +575,50 @@ impl S { ); } +#[test] +fn infer_self_as_path() { + assert_snapshot!( + infer(r#" +struct S1; +struct S2(isize); +enum E { + V1, + V2(u32), +} + +impl S1 { + fn test() { + Self; + } +} +impl S2 { + fn test() { + Self(1); + } +} +impl E { + fn test() { + Self::V1; + Self::V2(1); + } +} +"#), + @r###" + 87..108 '{ ... }': () + 97..101 'Self': S1 + 135..159 '{ ... }': () + 145..149 'Self': S2(isize) -> S2 + 145..152 'Self(1)': S2 + 150..151 '1': isize + 185..231 '{ ... }': () + 195..203 'Self::V1': E + 213..221 'Self::V2': V2(u32) -> E + 213..224 'Self::V2(1)': E + 222..223 '1': u32 + "### + ); +} + #[test] fn infer_binary_op() { assert_snapshot!( @@ -935,7 +979,7 @@ fn foo() { 29..33 'true': bool 34..51 '{ ... }': i32 44..45 '1': i32 - 57..80 '{ ... }': ! + 57..80 '{ ... }': i32 67..73 'return': ! 90..93 '_x2': i32 96..149 'if tru... }': i32 @@ -951,7 +995,7 @@ fn foo() { 186..190 'true': bool 194..195 '3': i32 205..206 '_': bool - 210..241 '{ ... }': ! + 210..241 '{ ... }': i32 224..230 'return': ! 257..260 '_x4': i32 263..320 'match ... }': i32 @@ -1687,7 +1731,7 @@ fn foo() -> u32 { 17..59 '{ ...; }; }': () 27..28 'x': || -> usize 31..56 '|| -> ...n 1; }': || -> usize - 43..56 '{ return 1; }': ! + 43..56 '{ return 1; }': usize 45..53 'return 1': ! 52..53 '1': usize "### @@ -1706,7 +1750,7 @@ fn foo() -> u32 { 17..48 '{ ...; }; }': () 27..28 'x': || -> () 31..45 '|| { return; }': || -> () - 34..45 '{ return; }': ! + 34..45 '{ return; }': () 36..42 'return': ! "### ); @@ -1755,3 +1799,127 @@ fn main() { "### ); } + +#[test] +fn effects_smoke_test() { + assert_snapshot!( + infer(r#" +fn main() { + let x = unsafe { 92 }; + let y = async { async { () }.await }; + let z = try { () }; + let t = 'a: { 92 }; +} +"#), + @r###" + 11..131 '{ ...2 }; }': () + 21..22 'x': i32 + 32..38 '{ 92 }': i32 + 34..36 '92': i32 + 48..49 'y': {unknown} + 58..80 '{ asyn...wait }': {unknown} + 60..78 'async ....await': {unknown} + 66..72 '{ () }': () + 68..70 '()': () + 90..91 'z': {unknown} + 94..104 'try { () }': {unknown} + 98..104 '{ () }': () + 100..102 '()': () + 114..115 't': i32 + 122..128 '{ 92 }': i32 + 124..126 '92': i32 + "### + ) +} + +#[test] +fn infer_generic_from_later_assignment() { + assert_snapshot!( + infer(r#" +enum Option { Some(T), None } +use Option::*; + +fn test() { + let mut end = None; + loop { + end = Some(true); + } +} +"#), + @r###" + 60..130 '{ ... } }': () + 70..77 'mut end': Option + 80..84 'None': Option + 90..128 'loop {... }': ! + 95..128 '{ ... }': () + 105..108 'end': Option + 105..121 'end = ...(true)': () + 111..115 'Some': Some(bool) -> Option + 111..121 'Some(true)': Option + 116..120 'true': bool + "### + ); +} + +#[test] +fn infer_loop_break_with_val() { + assert_snapshot!( + infer(r#" +enum Option { Some(T), None } +use Option::*; + +fn test() { + let x = loop { + if false { + break None; + } + + break Some(true); + }; +} +"#), + @r###" + 60..169 '{ ... }; }': () + 70..71 'x': Option + 74..166 'loop {... }': Option + 79..166 '{ ... }': () + 89..133 'if fal... }': () + 92..97 'false': bool + 98..133 '{ ... }': () + 112..122 'break None': ! + 118..122 'None': Option + 143..159 'break ...(true)': ! + 149..153 'Some': Some(bool) -> Option + 149..159 'Some(true)': Option + 154..158 'true': bool + "### + ); +} + +#[test] +fn infer_loop_break_without_val() { + assert_snapshot!( + infer(r#" +enum Option { Some(T), None } +use Option::*; + +fn test() { + let x = loop { + if false { + break; + } + }; +} +"#), + @r###" + 60..137 '{ ... }; }': () + 70..71 'x': () + 74..134 'loop {... }': () + 79..134 '{ ... }': () + 89..128 'if fal... }': () + 92..97 'false': bool + 98..128 '{ ... }': () + 112..117 'break': ! + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index e555c879a045..34f4b9039cc3 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs @@ -1,9 +1,10 @@ use insta::assert_snapshot; - use ra_db::fixture::WithFixture; +use test_utils::mark; + +use crate::test_db::TestDB; use super::{infer, infer_with_mismatches, type_at, type_at_pos}; -use crate::test_db::TestDB; #[test] fn infer_await() { @@ -301,7 +302,7 @@ fn test() { #[test] fn trait_default_method_self_bound_implements_trait() { - test_utils::covers!(trait_self_implements_self); + mark::check!(trait_self_implements_self); assert_snapshot!( infer(r#" trait Trait { @@ -324,7 +325,6 @@ trait Trait { #[test] fn trait_default_method_self_bound_implements_super_trait() { - test_utils::covers!(trait_self_implements_self); assert_snapshot!( infer(r#" trait SuperTrait { @@ -1616,6 +1616,138 @@ fn test u128>(f: F) { ); } +#[test] +fn fn_ptr_and_item() { + assert_snapshot!( + infer(r#" +#[lang="fn_once"] +trait FnOnce { + type Output; + + fn call_once(self, args: Args) -> Self::Output; +} + +trait Foo { + fn foo(&self) -> T; +} + +struct Bar(T); + +impl R> Foo<(A1, R)> for Bar { + fn foo(&self) -> (A1, R) {} +} + +enum Opt { None, Some(T) } +impl Opt { + fn map U>(self, f: F) -> Opt {} +} + +fn test() { + let bar: Bar u32>; + bar.foo(); + + let opt: Opt; + let f: fn(u8) -> u32; + opt.map(f); +} +"#), + @r###" +75..79 'self': Self +81..85 'args': Args +140..144 'self': &Self +244..248 'self': &Bar +261..263 '{}': () +347..351 'self': Opt +353..354 'f': F +369..371 '{}': () +385..501 '{ ...(f); }': () +395..398 'bar': Bar u32> +424..427 'bar': Bar u32> +424..433 'bar.foo()': {unknown} +444..447 'opt': Opt +466..467 'f': fn(u8) -> u32 +488..491 'opt': Opt +488..498 'opt.map(f)': Opt u32, (u8,)>> +496..497 'f': fn(u8) -> u32 +"### + ); +} + +#[test] +fn fn_trait_deref_with_ty_default() { + assert_snapshot!( + infer(r#" +#[lang = "deref"] +trait Deref { + type Target; + + fn deref(&self) -> &Self::Target; +} + +#[lang="fn_once"] +trait FnOnce { + type Output; + + fn call_once(self, args: Args) -> Self::Output; +} + +struct Foo; + +impl Foo { + fn foo(&self) -> usize {} +} + +struct Lazy T>(F); + +impl Lazy { + pub fn new(f: F) -> Lazy {} +} + +impl T> Deref for Lazy { + type Target = T; +} + +fn test() { + let lazy1: Lazy = Lazy::new(|| Foo); + let r1 = lazy1.foo(); + + fn make_foo_fn() -> Foo {} + let make_foo_fn_ptr: fn() -> Foo = make_foo_fn; + let lazy2: Lazy = Lazy::new(make_foo_fn_ptr); + let r2 = lazy2.foo(); +} +"#), + @r###" +65..69 'self': &Self +166..170 'self': Self +172..176 'args': Args +240..244 'self': &Foo +255..257 '{}': () +335..336 'f': F +355..357 '{}': () +444..690 '{ ...o(); }': () +454..459 'lazy1': Lazy T> +476..485 'Lazy::new': fn new T>(fn() -> T) -> Lazy T> +476..493 'Lazy::...| Foo)': Lazy T> +486..492 '|| Foo': || -> T +489..492 'Foo': Foo +503..505 'r1': {unknown} +508..513 'lazy1': Lazy T> +508..519 'lazy1.foo()': {unknown} +561..576 'make_foo_fn_ptr': fn() -> Foo +592..603 'make_foo_fn': fn make_foo_fn() -> Foo +613..618 'lazy2': Lazy T> +635..644 'Lazy::new': fn new T>(fn() -> T) -> Lazy T> +635..661 'Lazy::...n_ptr)': Lazy T> +645..660 'make_foo_fn_ptr': fn() -> Foo +671..673 'r2': {unknown} +676..681 'lazy2': Lazy T> +676..687 'lazy2.foo()': {unknown} +550..552 '{}': () +"### + ); +} + #[test] fn closure_1() { assert_snapshot!( @@ -2055,7 +2187,7 @@ fn test>>() { #[test] fn proc_macro_server_types() { assert_snapshot!( - infer_with_mismatches(r#" + infer(r#" macro_rules! with_api { ($S:ident, $self:ident, $m:ident) => { $m! { @@ -2069,9 +2201,9 @@ macro_rules! with_api { } macro_rules! associated_item { (type TokenStream) => - (type TokenStream: 'static + Clone;); + (type TokenStream: 'static;); (type Group) => - (type Group: 'static + Clone;); + (type Group: 'static;); ($($item:tt)*) => ($($item)*;) } macro_rules! declare_server_traits { @@ -2083,21 +2215,23 @@ macro_rules! declare_server_traits { } $(pub trait $name: Types { - $(associated_item!(fn $method(&mut self, $($arg: $arg_ty),*) $(-> $ret_ty)?);)* + $(associated_item!(fn $method($($arg: $arg_ty),*) $(-> $ret_ty)?);)* })* pub trait Server: Types $(+ $name)* {} impl Server for S {} } } + with_api!(Self, self_, declare_server_traits); -struct Group {} -struct TokenStream {} +struct G {} +struct T {} struct Rustc; impl Types for Rustc { - type TokenStream = TokenStream; - type Group = Group; + type TokenStream = T; + type Group = G; } + fn make() -> T { loop {} } impl TokenStream for Rustc { fn new() -> Self::TokenStream { @@ -2105,17 +2239,17 @@ impl TokenStream for Rustc { make() } } -"#, true), +"#), @r###" - 1115..1126 '{ loop {} }': T - 1117..1124 'loop {}': ! - 1122..1124 '{}': () - 1190..1253 '{ ... }': {unknown} - 1204..1209 'group': {unknown} - 1225..1229 'make': fn make<{unknown}>() -> {unknown} - 1225..1231 'make()': {unknown} - 1241..1245 'make': fn make<{unknown}>() -> {unknown} - 1241..1247 'make()': {unknown} + 1062..1073 '{ loop {} }': T + 1064..1071 'loop {}': ! + 1069..1071 '{}': () + 1137..1200 '{ ... }': T + 1151..1156 'group': G + 1172..1176 'make': fn make() -> G + 1172..1178 'make()': G + 1188..1192 'make': fn make() -> T + 1188..1194 'make()': T "### ); } diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index 1ccb7c3b4ab8..5870618a00a7 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -182,7 +182,10 @@ impl chalk_ir::interner::Interner for Interner { Arc::new(goal) } - fn intern_goals(&self, data: impl IntoIterator>) -> Self::InternedGoals { + fn intern_goals( + &self, + data: impl IntoIterator, E>>, + ) -> Result { data.into_iter().collect() } @@ -222,10 +225,10 @@ impl chalk_ir::interner::Interner for Interner { clause } - fn intern_program_clauses( + fn intern_program_clauses( &self, - data: impl IntoIterator>, - ) -> Arc<[chalk_ir::ProgramClause]> { + data: impl IntoIterator, E>>, + ) -> Result]>, E> { data.into_iter().collect() } @@ -236,10 +239,10 @@ impl chalk_ir::interner::Interner for Interner { &clauses } - fn intern_quantified_where_clauses( + fn intern_quantified_where_clauses( &self, - data: impl IntoIterator>, - ) -> Self::InternedQuantifiedWhereClauses { + data: impl IntoIterator, E>>, + ) -> Result { data.into_iter().collect() } @@ -250,10 +253,10 @@ impl chalk_ir::interner::Interner for Interner { clauses } - fn intern_parameter_kinds( + fn intern_parameter_kinds( &self, - data: impl IntoIterator>, - ) -> Self::InternedParameterKinds { + data: impl IntoIterator, E>>, + ) -> Result { data.into_iter().collect() } @@ -264,10 +267,10 @@ impl chalk_ir::interner::Interner for Interner { ¶meter_kinds } - fn intern_canonical_var_kinds( + fn intern_canonical_var_kinds( &self, - data: impl IntoIterator>, - ) -> Self::InternedCanonicalVarKinds { + data: impl IntoIterator, E>>, + ) -> Result { data.into_iter().collect() } @@ -460,6 +463,14 @@ impl ToChalk for TypeCtor { TypeName::Struct(struct_id) => db.lookup_intern_type_ctor(struct_id.into()), TypeName::AssociatedType(type_id) => TypeCtor::AssociatedType(from_chalk(db, type_id)), TypeName::OpaqueType(_) => unreachable!(), + + TypeName::Scalar(_) => unreachable!(), + TypeName::Tuple(_) => unreachable!(), + TypeName::Raw(_) => unreachable!(), + TypeName::Slice => unreachable!(), + TypeName::Ref(_) => unreachable!(), + TypeName::Str => unreachable!(), + TypeName::Error => { // this should not be reached, since we don't represent TypeName::Error with TypeCtor unreachable!() @@ -862,12 +873,6 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { // We don't do coherence checking (yet) unimplemented!() } - fn as_struct_id(&self, id: &TypeName) -> Option { - match id { - TypeName::Struct(struct_id) => Some(*struct_id), - _ => None, - } - } fn interner(&self) -> &Interner { &Interner } @@ -892,6 +897,20 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { ) -> Arc> { unimplemented!() } + + fn force_impl_for( + &self, + _well_known: chalk_rust_ir::WellKnownTrait, + _ty: &chalk_ir::TyData, + ) -> Option { + // this method is mostly for rustc + None + } + + fn is_object_safe(&self, _trait_id: chalk_ir::TraitId) -> bool { + // FIXME: implement actual object safety + true + } } pub(crate) fn program_clauses_for_chalk_env_query( diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs deleted file mode 100644 index 2b5d11681af7..000000000000 --- a/crates/ra_ide/src/assists.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! FIXME: write short doc here - -use ra_assists::{resolved_assists, AssistAction, AssistLabel}; -use ra_db::{FilePosition, FileRange}; -use ra_ide_db::RootDatabase; - -use crate::{FileId, SourceChange, SourceFileEdit}; - -pub use ra_assists::AssistId; - -#[derive(Debug)] -pub struct Assist { - pub id: AssistId, - pub label: String, - pub group_label: Option, - pub source_change: SourceChange, -} - -pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec { - resolved_assists(db, frange) - .into_iter() - .map(|assist| { - let file_id = frange.file_id; - let assist_label = &assist.label; - Assist { - id: assist_label.id, - label: assist_label.label.clone(), - group_label: assist.group_label.map(|it| it.0), - source_change: action_to_edit(assist.action, file_id, assist_label), - } - }) - .collect() -} - -fn action_to_edit( - action: AssistAction, - file_id: FileId, - assist_label: &AssistLabel, -) -> SourceChange { - let file_id = match action.file { - ra_assists::AssistFile::TargetFile(it) => it, - _ => file_id, - }; - let file_edit = SourceFileEdit { file_id, edit: action.edit }; - SourceChange::source_file_edit(assist_label.label.clone(), file_edit) - .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) -} diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs index 780a03c1380e..aa039e6fcdb2 100644 --- a/crates/ra_ide/src/call_info.rs +++ b/crates/ra_ide/src/call_info.rs @@ -5,7 +5,7 @@ use ra_syntax::{ ast::{self, ArgListOwner}, match_ast, AstNode, SyntaxNode, SyntaxToken, }; -use test_utils::tested_by; +use test_utils::mark; use crate::{CallInfo, FilePosition, FunctionSignature}; @@ -84,7 +84,7 @@ fn call_info_for_token(sema: &Semantics, token: SyntaxToken) -> Op let arg_list_range = arg_list.syntax().text_range(); if !arg_list_range.contains_inclusive(token.text_range().start()) { - tested_by!(call_info_bad_offset); + mark::hit!(call_info_bad_offset); return None; } @@ -213,7 +213,7 @@ impl CallInfo { #[cfg(test)] mod tests { - use test_utils::covers; + use test_utils::mark; use crate::mock_analysis::single_file_with_position; @@ -529,7 +529,7 @@ By default this method stops actor's `Context`."# #[test] fn call_info_bad_offset() { - covers!(call_info_bad_offset); + mark::check!(call_info_bad_offset); let (analysis, position) = single_file_with_position( r#"fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo <|> (3, ); }"#, diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 4ca0fdf4fbfe..191300704b55 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -59,13 +59,13 @@ pub use crate::completion::{ /// with ordering of completions (currently this is done by the client). pub(crate) fn completions( db: &RootDatabase, - position: FilePosition, config: &CompletionConfig, + position: FilePosition, ) -> Option { let ctx = CompletionContext::new(db, position, config)?; let mut acc = Completions::default(); - + complete_attribute::complete_attribute(&mut acc, &ctx); complete_fn_param::complete_fn_param(&mut acc, &ctx); complete_keyword::complete_expr_keyword(&mut acc, &ctx); complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); @@ -79,7 +79,6 @@ pub(crate) fn completions( complete_postfix::complete_postfix(&mut acc, &ctx); complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); complete_trait_impl::complete_trait_impl(&mut acc, &ctx); - complete_attribute::complete_attribute(&mut acc, &ctx); Some(acc) } diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf9527980e2..f17266221fd6 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs @@ -3,25 +3,29 @@ //! This module uses a bit of static metadata to provide completions //! for built-in attributes. -use super::completion_context::CompletionContext; -use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; -use ra_syntax::{ - ast::{Attr, AttrKind}, - AstNode, +use ra_syntax::{ast, AstNode, SyntaxKind}; +use rustc_hash::FxHashSet; + +use crate::completion::{ + completion_context::CompletionContext, + completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, }; -pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_attribute { - return; +pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let attribute = ctx.attribute_under_caret.as_ref()?; + + match (attribute.path(), attribute.input()) { + (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) + if path.to_string() == "derive" => + { + complete_derive(acc, ctx, token_tree) + } + _ => complete_attribute_start(acc, ctx, attribute), } + Some(()) +} - let is_inner = ctx - .original_token - .ancestors() - .find_map(Attr::cast) - .map(|attr| attr.kind() == AttrKind::Inner) - .unwrap_or(false); - +fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { for attr_completion in ATTRIBUTES { let mut item = CompletionItem::new( CompletionKind::Attribute, @@ -37,7 +41,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) _ => {} } - if is_inner || !attr_completion.should_be_inner { + if attribute.kind() == ast::AttrKind::Inner || !attr_completion.should_be_inner { acc.add(item); } } @@ -126,6 +130,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[ }, ]; +fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { + if let Ok(existing_derives) = parse_derive_input(derive_input) { + for derive_completion in DEFAULT_DERIVE_COMPLETIONS + .into_iter() + .filter(|completion| !existing_derives.contains(completion.label)) + { + let mut label = derive_completion.label.to_owned(); + for dependency in derive_completion + .dependencies + .into_iter() + .filter(|&&dependency| !existing_derives.contains(dependency)) + { + label.push_str(", "); + label.push_str(dependency); + } + acc.add( + CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) + .kind(CompletionItemKind::Attribute), + ); + } + + for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { + acc.add( + CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + custom_derive_name, + ) + .kind(CompletionItemKind::Attribute), + ); + } + } +} + +fn parse_derive_input(derive_input: ast::TokenTree) -> Result, ()> { + match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { + (Some(left_paren), Some(right_paren)) + if left_paren.kind() == SyntaxKind::L_PAREN + && right_paren.kind() == SyntaxKind::R_PAREN => + { + let mut input_derives = FxHashSet::default(); + let mut current_derive = String::new(); + for token in derive_input + .syntax() + .children_with_tokens() + .filter_map(|token| token.into_token()) + .skip_while(|token| token != &left_paren) + .skip(1) + .take_while(|token| token != &right_paren) + { + if SyntaxKind::COMMA == token.kind() { + if !current_derive.is_empty() { + input_derives.insert(current_derive); + current_derive = String::new(); + } + } else { + current_derive.push_str(token.to_string().trim()); + } + } + + if !current_derive.is_empty() { + input_derives.insert(current_derive); + } + Ok(input_derives) + } + _ => Err(()), + } +} + +fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet { + let mut result = FxHashSet::default(); + ctx.scope().process_all_names(&mut |name, scope_def| { + if let hir::ScopeDef::MacroDef(mac) = scope_def { + if mac.is_derive_macro() { + result.insert(name.to_string()); + } + } + }); + result +} + +struct DeriveCompletion { + label: &'static str, + dependencies: &'static [&'static str], +} + +/// Standard Rust derives and the information about their dependencies +/// (the dependencies are needed so that the main derive don't break the compilation when added) +const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ + DeriveCompletion { label: "Clone", dependencies: &[] }, + DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, + DeriveCompletion { label: "Debug", dependencies: &[] }, + DeriveCompletion { label: "Default", dependencies: &[] }, + DeriveCompletion { label: "Hash", dependencies: &[] }, + DeriveCompletion { label: "PartialEq", dependencies: &[] }, + DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, + DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, + DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, +]; + #[cfg(test)] mod tests { use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; @@ -135,6 +239,170 @@ mod tests { do_completion(code, CompletionKind::Attribute) } + #[test] + fn empty_derive_completion() { + assert_debug_snapshot!( + do_attr_completion( + r" + #[derive(<|>)] + struct Test {} + ", + ), + @r###" + [ + CompletionItem { + label: "Clone", + source_range: 30..30, + delete: 30..30, + insert: "Clone", + kind: Attribute, + }, + CompletionItem { + label: "Copy, Clone", + source_range: 30..30, + delete: 30..30, + insert: "Copy, Clone", + kind: Attribute, + }, + CompletionItem { + label: "Debug", + source_range: 30..30, + delete: 30..30, + insert: "Debug", + kind: Attribute, + }, + CompletionItem { + label: "Default", + source_range: 30..30, + delete: 30..30, + insert: "Default", + kind: Attribute, + }, + CompletionItem { + label: "Eq, PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "Eq, PartialEq", + kind: Attribute, + }, + CompletionItem { + label: "Hash", + source_range: 30..30, + delete: 30..30, + insert: "Hash", + kind: Attribute, + }, + CompletionItem { + label: "Ord, PartialOrd, Eq, PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "Ord, PartialOrd, Eq, PartialEq", + kind: Attribute, + }, + CompletionItem { + label: "PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "PartialEq", + kind: Attribute, + }, + CompletionItem { + label: "PartialOrd, PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "PartialOrd, PartialEq", + kind: Attribute, + }, + ] + "### + ); + } + + #[test] + fn no_completion_for_incorrect_derive() { + assert_debug_snapshot!( + do_attr_completion( + r" + #[derive{<|>)] + struct Test {} + ", + ), + @"[]" + ); + } + + #[test] + fn derive_with_input_completion() { + assert_debug_snapshot!( + do_attr_completion( + r" + #[derive(serde::Serialize, PartialEq, <|>)] + struct Test {} + ", + ), + @r###" + [ + CompletionItem { + label: "Clone", + source_range: 59..59, + delete: 59..59, + insert: "Clone", + kind: Attribute, + }, + CompletionItem { + label: "Copy, Clone", + source_range: 59..59, + delete: 59..59, + insert: "Copy, Clone", + kind: Attribute, + }, + CompletionItem { + label: "Debug", + source_range: 59..59, + delete: 59..59, + insert: "Debug", + kind: Attribute, + }, + CompletionItem { + label: "Default", + source_range: 59..59, + delete: 59..59, + insert: "Default", + kind: Attribute, + }, + CompletionItem { + label: "Eq", + source_range: 59..59, + delete: 59..59, + insert: "Eq", + kind: Attribute, + }, + CompletionItem { + label: "Hash", + source_range: 59..59, + delete: 59..59, + insert: "Hash", + kind: Attribute, + }, + CompletionItem { + label: "Ord, PartialOrd, Eq", + source_range: 59..59, + delete: 59..59, + insert: "Ord, PartialOrd, Eq", + kind: Attribute, + }, + CompletionItem { + label: "PartialOrd", + source_range: 59..59, + delete: 59..59, + insert: "PartialOrd", + kind: Attribute, + }, + ] + "### + ); + } + #[test] fn test_attribute_completion() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 6a0f0c72e2fa..f2a52a407a97 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs @@ -14,6 +14,7 @@ use crate::{ }, CompletionItem, }; +use ra_assists::utils::TryEnum; pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { if !ctx.config.enable_postfix_completions { @@ -37,8 +38,53 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { Some(it) => it, None => return, }; + let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); + if let Some(try_enum) = &try_enum { + match try_enum { + TryEnum::Result => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "ifl", + "if let Ok {}", + &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); - if receiver_ty.is_bool() || receiver_ty.is_unknown() { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "while", + "while let Ok {}", + &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + } + TryEnum::Option => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "ifl", + "if let Some {}", + &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "while", + "while let Some {}", + &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + } + } + } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { postfix_snippet( ctx, cap, @@ -58,7 +104,6 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { ) .add_to(acc); } - // !&&&42 is a compiler error, ergo process it before considering the references postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) .add_to(acc); @@ -80,16 +125,45 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { let dot_receiver = include_references(dot_receiver); let receiver_text = get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); - - postfix_snippet( - ctx, - cap, - &dot_receiver, - "match", - "match expr {}", - &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), - ) - .add_to(acc); + match try_enum { + Some(try_enum) => { + match try_enum { + TryEnum::Result => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "match", + "match expr {}", + &format!("match {} {{\n Ok(${{1:_}}) => {{$2\\}},\n Err(${{3:_}}) => {{$0\\}},\n}}", receiver_text), + ) + .add_to(acc); + } + TryEnum::Option => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "match", + "match expr {}", + &format!("match {} {{\n Some(${{1:_}}) => {{$2\\}},\n None => {{$0\\}},\n}}", receiver_text), + ) + .add_to(acc); + } + } + } + None => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "match", + "match expr {}", + &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), + ) + .add_to(acc); + } + } postfix_snippet( ctx, @@ -235,6 +309,164 @@ mod tests { ); } + #[test] + fn postfix_completion_works_for_option() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + enum Option { + Some(T), + None, + } + + fn main() { + let bar = Option::Some(true); + bar.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: 210..210, + delete: 206..210, + insert: "Box::new(bar)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: 210..210, + delete: 206..210, + insert: "dbg!(bar)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "ifl", + source_range: 210..210, + delete: 206..210, + insert: "if let Some($1) = bar {\n $0\n}", + detail: "if let Some {}", + }, + CompletionItem { + label: "match", + source_range: 210..210, + delete: 206..210, + insert: "match bar {\n Some(${1:_}) => {$2\\},\n None => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: 210..210, + delete: 206..210, + insert: "!bar", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: 210..210, + delete: 206..210, + insert: "&bar", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: 210..210, + delete: 206..210, + insert: "&mut bar", + detail: "&mut expr", + }, + CompletionItem { + label: "while", + source_range: 210..210, + delete: 206..210, + insert: "while let Some($1) = bar {\n $0\n}", + detail: "while let Some {}", + }, + ] + "### + ); + } + + #[test] + fn postfix_completion_works_for_result() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + enum Result { + Ok(T), + Err(E), + } + + fn main() { + let bar = Result::Ok(true); + bar.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: 211..211, + delete: 207..211, + insert: "Box::new(bar)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: 211..211, + delete: 207..211, + insert: "dbg!(bar)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "ifl", + source_range: 211..211, + delete: 207..211, + insert: "if let Ok($1) = bar {\n $0\n}", + detail: "if let Ok {}", + }, + CompletionItem { + label: "match", + source_range: 211..211, + delete: 207..211, + insert: "match bar {\n Ok(${1:_}) => {$2\\},\n Err(${3:_}) => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: 211..211, + delete: 207..211, + insert: "!bar", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: 211..211, + delete: 207..211, + insert: "&bar", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: 211..211, + delete: 207..211, + insert: "&mut bar", + detail: "&mut expr", + }, + CompletionItem { + label: "while", + source_range: 211..211, + delete: 207..211, + insert: "while let Ok($1) = bar {\n $0\n}", + detail: "while let Ok {}", + }, + ] + "### + ); + } + #[test] fn some_postfix_completions_ignored() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs index aa56a5cd8703..02ac0166b6e1 100644 --- a/crates/ra_ide/src/completion/complete_qualified_path.rs +++ b/crates/ra_ide/src/completion/complete_qualified_path.rs @@ -2,20 +2,25 @@ use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; use ra_syntax::AstNode; -use test_utils::tested_by; +use rustc_hash::FxHashSet; +use test_utils::mark; use crate::completion::{CompletionContext, Completions}; -use rustc_hash::FxHashSet; pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { let path = match &ctx.path_prefix { Some(path) => path.clone(), - _ => return, + None => return, }; + + if ctx.attribute_under_caret.is_some() { + return; + } + let scope = ctx.scope(); let context_module = scope.module(); - let res = match scope.resolve_hir_path(&path) { + let res = match scope.resolve_hir_path_qualifier(&path) { Some(res) => res, None => return, }; @@ -35,7 +40,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { if name_ref.syntax().text() == name.to_string().as_str() { // for `use self::foo<|>`, don't suggest `foo` as a completion - tested_by!(dont_complete_current_use); + mark::hit!(dont_complete_current_use); continue; } } @@ -79,7 +84,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon }); // Iterate assoc types separately - ty.iterate_impl_items(ctx.db, krate, |item| { + ty.iterate_assoc_items(ctx.db, krate, |item| { if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { return None; } @@ -142,7 +147,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon #[cfg(test)] mod tests { - use test_utils::covers; + use test_utils::mark; use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; use insta::assert_debug_snapshot; @@ -153,7 +158,7 @@ mod tests { #[test] fn dont_complete_current_use() { - covers!(dont_complete_current_use); + mark::check!(dont_complete_current_use); let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); assert!(completions.is_empty()); } @@ -220,6 +225,34 @@ mod tests { ); } + #[test] + fn completes_mod_with_same_name_as_function() { + assert_debug_snapshot!( + do_reference_completion( + r" + use self::my::<|>; + + mod my { + pub struct Bar; + } + + fn my() {} + " + ), + @r###" + [ + CompletionItem { + label: "Bar", + source_range: 31..31, + delete: 31..31, + insert: "Bar", + kind: Struct, + }, + ] + "### + ); + } + #[test] fn path_visibility() { assert_debug_snapshot!( @@ -1325,4 +1358,18 @@ mod tests { "### ); } + + #[test] + fn dont_complete_attr() { + assert_debug_snapshot!( + do_reference_completion( + r" + mod foo { pub struct Foo; } + #[foo::<|>] + fn f() {} + " + ), + @r###"[]"### + ) + } } diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs index a3f5d1b6a017..0568d9ccf395 100644 --- a/crates/ra_ide/src/completion/complete_snippet.rs +++ b/crates/ra_ide/src/completion/complete_snippet.rs @@ -33,6 +33,24 @@ pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionConte None => return, }; + snippet( + ctx, + cap, + "Test module", + "\ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ${1:test_name}() { + $0 + } +}", + ) + .lookup_by("tmod") + .add_to(acc); + snippet( ctx, cap, @@ -117,6 +135,14 @@ mod tests { kind: Snippet, lookup: "tfn", }, + CompletionItem { + label: "Test module", + source_range: 78..78, + delete: 78..78, + insert: "#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn ${1:test_name}() {\n $0\n }\n}", + kind: Snippet, + lookup: "tmod", + }, CompletionItem { label: "macro_rules", source_range: 78..78, diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index ee32d1ff600b..039df03e0547 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs @@ -32,7 +32,7 @@ //! ``` use hir::{self, Docs, HasSource}; -use ra_assists::utils::get_missing_impl_items; +use ra_assists::utils::get_missing_assoc_items; use ra_syntax::{ ast::{self, edit, ImplDef}, AstNode, SyntaxKind, SyntaxNode, TextRange, T, @@ -50,7 +50,7 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext if let Some((trigger, impl_def)) = completion_match(ctx) { match trigger.kind() { SyntaxKind::NAME_REF => { - get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { + get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { hir::AssocItem::Function(fn_item) => { add_function_impl(&trigger, acc, ctx, &fn_item) } @@ -64,34 +64,40 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext } SyntaxKind::FN_DEF => { - for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( - |item| match item { - hir::AssocItem::Function(fn_item) => Some(fn_item), - _ => None, - }, - ) { + for missing_fn in + get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { + match item { + hir::AssocItem::Function(fn_item) => Some(fn_item), + _ => None, + } + }) + { add_function_impl(&trigger, acc, ctx, &missing_fn); } } SyntaxKind::TYPE_ALIAS_DEF => { - for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( - |item| match item { - hir::AssocItem::TypeAlias(type_item) => Some(type_item), - _ => None, - }, - ) { + for missing_fn in + get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { + match item { + hir::AssocItem::TypeAlias(type_item) => Some(type_item), + _ => None, + } + }) + { add_type_alias_impl(&trigger, acc, ctx, &missing_fn); } } SyntaxKind::CONST_DEF => { - for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( - |item| match item { - hir::AssocItem::Const(const_item) => Some(const_item), - _ => None, - }, - ) { + for missing_fn in + get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { + match item { + hir::AssocItem::Const(const_item) => Some(const_item), + _ => None, + } + }) + { add_const_impl(&trigger, acc, ctx, &missing_fn); } } diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index a6a5568de0bf..db791660a18a 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs @@ -1,16 +1,19 @@ //! Completion of names from the current scope, e.g. locals and imported items. use hir::ScopeDef; -use test_utils::tested_by; +use test_utils::mark; use crate::completion::{CompletionContext, Completions}; use hir::{Adt, ModuleDef, Type}; use ra_syntax::AstNode; pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { - if (!ctx.is_trivial_path && !ctx.is_pat_binding_or_const) - || ctx.record_lit_syntax.is_some() + if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { + return; + } + if ctx.record_lit_syntax.is_some() || ctx.record_pat_syntax.is_some() + || ctx.attribute_under_caret.is_some() { return; } @@ -27,7 +30,7 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC if ctx.use_item_syntax.is_some() { if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { if name_ref.syntax().text() == name.to_string().as_str() { - tested_by!(self_fulfilling_completion); + mark::hit!(self_fulfilling_completion); return; } } @@ -63,7 +66,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T #[cfg(test)] mod tests { use insta::assert_debug_snapshot; - use test_utils::covers; + use test_utils::mark; use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; @@ -73,7 +76,7 @@ mod tests { #[test] fn self_fulfilling_completion() { - covers!(self_fulfilling_completion); + mark::check!(self_fulfilling_completion); assert_debug_snapshot!( do_reference_completion( r#" @@ -1369,4 +1372,18 @@ mod tests { "### ) } + + #[test] + fn dont_complete_attr() { + assert_debug_snapshot!( + do_reference_completion( + r" + struct Foo; + #[<|>] + fn f() {} + " + ), + @r###"[]"### + ) + } } diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 118fceb2e746..da336973c180 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -9,7 +9,7 @@ use ra_syntax::{ SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, }; -use ra_text_edit::AtomTextEdit; +use ra_text_edit::Indel; use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; @@ -34,7 +34,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) record_pat_syntax: Option, pub(super) record_field_syntax: Option, pub(super) impl_def: Option, - /// FIXME: `ActiveParameter` is string-based, which is very wrong + /// FIXME: `ActiveParameter` is string-based, which is very very wrong pub(super) active_parameter: Option, pub(super) is_param: bool, /// If a name-binding or reference to a const in a pattern. @@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_macro_call: bool, pub(super) is_path_type: bool, pub(super) has_type_args: bool, - pub(super) is_attribute: bool, + pub(super) attribute_under_caret: Option, } impl<'a> CompletionContext<'a> { @@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> { // actual completion. let file_with_fake_ident = { let parse = db.parse(position.file_id); - let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); + let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); parse.reparse(&edit).tree() }; let fake_ident_token = @@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> { is_path_type: false, has_type_args: false, dot_receiver_is_ambiguous_float_literal: false, - is_attribute: false, + attribute_under_caret: None, }; let mut original_file = original_file.syntax().clone(); @@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> { Some(ty) }) .flatten(); + self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); // First, let's try to complete a reference to some declaration. if let Some(name_ref) = find_node_at_offset::(&file_with_fake_ident, offset) { @@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> { .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) .is_some(); self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); - self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some(); self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); self.has_type_args = segment.type_arg_list().is_some(); @@ -344,7 +344,7 @@ impl<'a> CompletionContext<'a> { stmt.syntax().text_range() == name_ref.syntax().text_range(), ); } - if let Some(block) = ast::Block::cast(node) { + if let Some(block) = ast::BlockExpr::cast(node) { return Some( block.expr().map(|e| e.syntax().text_range()) == Some(name_ref.syntax().text_range()), diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index 5936fb8f7c23..cfb7c1e380b6 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs @@ -2,11 +2,12 @@ use std::fmt; -use super::completion_config::SnippetCap; use hir::Documentation; use ra_syntax::TextRange; use ra_text_edit::TextEdit; +use crate::completion::completion_config::SnippetCap; + /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a /// `CompletionItem`, use `new` method and the `Builder` struct. @@ -62,8 +63,8 @@ impl fmt::Debug for CompletionItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut s = f.debug_struct("CompletionItem"); s.field("label", &self.label()).field("source_range", &self.source_range()); - if self.text_edit().as_atoms().len() == 1 { - let atom = &self.text_edit().as_atoms()[0]; + if self.text_edit().len() == 1 { + let atom = &self.text_edit().iter().next().unwrap(); s.field("delete", &atom.delete); s.field("insert", &atom.insert); } else { diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 2edb130cf7bf..440ffa31d4e0 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs @@ -3,7 +3,7 @@ use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; use ra_syntax::ast::NameOwner; use stdx::SepBy; -use test_utils::tested_by; +use test_utils::mark; use crate::{ completion::{ @@ -17,12 +17,11 @@ use crate::{ impl Completions { pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { let is_deprecated = is_deprecated(field, ctx.db); - let ty = ty.display(ctx.db).to_string(); let name = field.name(ctx.db); let mut completion_item = CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) .kind(CompletionItemKind::Field) - .detail(ty.clone()) + .detail(ty.display(ctx.db).to_string()) .set_documentation(field.docs(ctx.db)) .set_deprecated(is_deprecated); @@ -107,6 +106,12 @@ impl Completions { } }; + if let ScopeDef::Local(local) = resolution { + if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) { + completion_item = completion_item.set_score(score); + } + } + // Add `<>` for generic types if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { if let Some(cap) = ctx.config.snippet_cap { @@ -116,7 +121,7 @@ impl Completions { _ => false, }; if has_non_default_type_params { - tested_by!(inserts_angle_brackets_for_generics); + mark::hit!(inserts_angle_brackets_for_generics); completion_item = completion_item .lookup_by(local_name.clone()) .label(format!("{}<…>", local_name)) @@ -171,7 +176,7 @@ impl Completions { } None if needs_bang => builder.insert_text(format!("{}!", name)), _ => { - tested_by!(dont_insert_macro_call_parens_unncessary); + mark::hit!(dont_insert_macro_call_parens_unncessary); builder.insert_text(name) } }; @@ -319,19 +324,20 @@ impl Completions { pub(crate) fn compute_score( ctx: &CompletionContext, - // FIXME: this definitely should be a `Type` - ty: &str, + ty: &Type, name: &str, ) -> Option { + // FIXME: this should not fall back to string equality. + let ty = &ty.display(ctx.db).to_string(); let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { - tested_by!(test_struct_field_completion_in_record_lit); + mark::hit!(test_struct_field_completion_in_record_lit); let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; ( struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db).display(ctx.db).to_string(), ) } else if let Some(active_parameter) = &ctx.active_parameter { - tested_by!(test_struct_field_completion_in_func_call); + mark::hit!(test_struct_field_completion_in_func_call); (active_parameter.name.clone(), active_parameter.ty.clone()) } else { return None; @@ -392,7 +398,7 @@ impl Builder { None => return self, }; // If not an import, add parenthesis automatically. - tested_by!(inserts_parens_for_function_calls); + mark::hit!(inserts_parens_for_function_calls); let (snippet, label) = if params.is_empty() { (format!("{}()$0", name), format!("{}()", name)) @@ -451,7 +457,7 @@ fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static s #[cfg(test)] mod tests { use insta::assert_debug_snapshot; - use test_utils::covers; + use test_utils::mark; use crate::completion::{ test_utils::{do_completion, do_completion_with_options}, @@ -601,7 +607,7 @@ mod tests { #[test] fn inserts_parens_for_function_calls() { - covers!(inserts_parens_for_function_calls); + mark::check!(inserts_parens_for_function_calls); assert_debug_snapshot!( do_reference_completion( r" @@ -986,7 +992,7 @@ mod tests { #[test] fn inserts_angle_brackets_for_generics() { - covers!(inserts_angle_brackets_for_generics); + mark::check!(inserts_angle_brackets_for_generics); assert_debug_snapshot!( do_reference_completion( r" @@ -1109,7 +1115,7 @@ mod tests { #[test] fn dont_insert_macro_call_parens_unncessary() { - covers!(dont_insert_macro_call_parens_unncessary); + mark::check!(dont_insert_macro_call_parens_unncessary); assert_debug_snapshot!( do_reference_completion( r" @@ -1175,7 +1181,7 @@ mod tests { #[test] fn test_struct_field_completion_in_func_call() { - covers!(test_struct_field_completion_in_func_call); + mark::check!(test_struct_field_completion_in_func_call); assert_debug_snapshot!( do_reference_completion( r" @@ -1265,7 +1271,7 @@ mod tests { #[test] fn test_struct_field_completion_in_record_lit() { - covers!(test_struct_field_completion_in_record_lit); + mark::check!(test_struct_field_completion_in_record_lit); assert_debug_snapshot!( do_reference_completion( r" @@ -1405,4 +1411,48 @@ mod tests { "### ); } + + #[test] + fn prioritize_exact_ref_match() { + assert_debug_snapshot!( + do_reference_completion( + r" + struct WorldSnapshot { _f: () }; + fn go(world: &WorldSnapshot) { + go(w<|>) + } + ", + ), + @r###" + [ + CompletionItem { + label: "WorldSnapshot", + source_range: 132..133, + delete: 132..133, + insert: "WorldSnapshot", + kind: Struct, + }, + CompletionItem { + label: "go(…)", + source_range: 132..133, + delete: 132..133, + insert: "go(${1:world})$0", + kind: Function, + lookup: "go", + detail: "fn go(world: &WorldSnapshot)", + trigger_call_info: true, + }, + CompletionItem { + label: "world", + source_range: 132..133, + delete: 132..133, + insert: "world", + kind: Binding, + detail: "&WorldSnapshot", + score: TypeAndNameMatch, + }, + ] + "### + ); + } } diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index eb90b5279c46..bf22452a281c 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs @@ -20,7 +20,7 @@ pub(crate) fn do_completion_with_options( } else { single_file_with_position(code) }; - let completions = analysis.completions(position, options).unwrap().unwrap(); + let completions = analysis.completions(options, position).unwrap().unwrap(); let completion_items: Vec = completions.into(); let mut kind_completions: Vec = completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index a6b4c2c28470..c2819bbf7b80 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs @@ -64,7 +64,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec .unwrap_or_else(|| RelativePath::new("")) .join(&d.candidate); let create_file = FileSystemEdit::CreateFile { source_root, path }; - let fix = SourceChange::file_system_edit("create module", create_file); + let fix = SourceChange::file_system_edit("Create module", create_file); res.borrow_mut().push(Diagnostic { range: sema.diagnostics_range(d).range, message: d.message(), @@ -92,7 +92,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); Some(SourceChange::source_file_edit_from( - "fill struct fields", + "Fill struct fields", file_id, builder.finish(), )) @@ -117,7 +117,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec let node = d.ast(db); let replacement = format!("Ok({})", node.syntax()); let edit = TextEdit::replace(node.syntax().text_range(), replacement); - let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); + let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit); res.borrow_mut().push(Diagnostic { range: sema.diagnostics_range(d).range, message: d.message(), @@ -199,7 +199,7 @@ fn check_struct_shorthand_initialization( message: "Shorthand struct initialization".to_string(), severity: Severity::WeakWarning, fix: Some(SourceChange::source_file_edit( - "use struct shorthand initialization", + "Use struct shorthand initialization", SourceFileEdit { file_id, edit }, )), }); @@ -241,7 +241,11 @@ mod tests { diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); let mut fix = diagnostic.fix.unwrap(); let edit = fix.source_file_edits.pop().unwrap().edit; - let actual = edit.apply(&before); + let actual = { + let mut actual = before.to_string(); + edit.apply(&mut actual); + actual + }; assert_eq_text!(after, &actual); } @@ -256,7 +260,11 @@ mod tests { let mut fix = diagnostic.fix.unwrap(); let edit = fix.source_file_edits.pop().unwrap().edit; let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); - let actual = edit.apply(&target_file_contents); + let actual = { + let mut actual = target_file_contents.to_string(); + edit.apply(&mut actual); + actual + }; // Strip indent and empty lines from `after`, to match the behaviour of // `parse_fixture` called from `analysis_and_position`. @@ -288,7 +296,11 @@ mod tests { let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); let mut fix = diagnostic.fix.unwrap(); let edit = fix.source_file_edits.pop().unwrap().edit; - let actual = edit.apply(&before); + let actual = { + let mut actual = before.to_string(); + edit.apply(&mut actual); + actual + }; assert_eq_text!(after, &actual); } @@ -606,7 +618,7 @@ mod tests { range: 0..8, fix: Some( SourceChange { - label: "create module", + label: "Create module", source_file_edits: [], file_system_edits: [ CreateFile { @@ -616,7 +628,7 @@ mod tests { path: "foo.rs", }, ], - cursor_position: None, + is_snippet: false, }, ), severity: Error, @@ -655,24 +667,24 @@ mod tests { range: 224..233, fix: Some( SourceChange { - label: "fill struct fields", + label: "Fill struct fields", source_file_edits: [ SourceFileEdit { file_id: FileId( 1, ), edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: 3..9, + indels: [ + Indel { insert: "{a:42, b: ()}", + delete: 3..9, }, ], }, }, ], file_system_edits: [], - cursor_position: None, + is_snippet: false, }, ), severity: Error, diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index db3907fe64e1..9572debd822c 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -1,5 +1,7 @@ //! FIXME: write short doc here +// FIXME: this modules relies on strings and AST way too much, and it should be +// rewritten (matklad 2020-05-07) use std::{ convert::From, fmt::{self, Display}, @@ -82,8 +84,8 @@ impl FunctionSignature { let ty = field.signature_ty(db); let raw_param = format!("{}", ty.display(db)); - if let Some(param_type) = raw_param.split(':').nth(1) { - parameter_types.push(param_type[1..].to_string()); + if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) { + parameter_types.push(param_type.to_string()); } else { // useful when you have tuple struct parameter_types.push(raw_param.clone()); @@ -127,8 +129,8 @@ impl FunctionSignature { for field in variant.fields(db).into_iter() { let ty = field.signature_ty(db); let raw_param = format!("{}", ty.display(db)); - if let Some(param_type) = raw_param.split(':').nth(1) { - parameter_types.push(param_type[1..].to_string()); + if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) { + parameter_types.push(param_type.to_string()); } else { // The unwrap_or_else is useful when you have tuple parameter_types.push(raw_param); @@ -195,14 +197,23 @@ impl From<&'_ ast::FnDef> for FunctionSignature { let raw_param = self_param.syntax().text().to_string(); res_types.push( - raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(), + raw_param + .split(':') + .nth(1) + .and_then(|it| it.get(1..)) + .unwrap_or_else(|| "Self") + .to_string(), ); res.push(raw_param); } res.extend(param_list.params().map(|param| param.syntax().text().to_string())); res_types.extend(param_list.params().map(|param| { - param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() + let param_text = param.syntax().text().to_string(); + match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { + Some(it) => it.to_string(), + None => param_text, + } })); } (has_self_param, res, res_types) diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 914a8b471e3f..5da28edd2414 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs @@ -11,7 +11,7 @@ use ra_syntax::{ TextRange, }; -use crate::FileSymbol; +use crate::{FileRange, FileSymbol}; use super::short_label::ShortLabel; @@ -22,10 +22,11 @@ use super::short_label::ShortLabel; /// code, like a function or a struct, but this is not strictly required. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NavigationTarget { + // FIXME: use FileRange? file_id: FileId, + full_range: TextRange, name: SmolStr, kind: SyntaxKind, - full_range: TextRange, focus_range: Option, container_name: Option, description: Option, @@ -63,6 +64,10 @@ impl NavigationTarget { self.file_id } + pub fn file_range(&self) -> FileRange { + FileRange { file_id: self.file_id, range: self.full_range } + } + pub fn full_range(&self) -> TextRange { self.full_range } @@ -376,16 +381,20 @@ impl ToNav for hir::Local { impl ToNav for hir::TypeParam { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { let src = self.source(db); - let range = match src.value { + let full_range = match &src.value { Either::Left(it) => it.syntax().text_range(), Either::Right(it) => it.syntax().text_range(), }; + let focus_range = match &src.value { + Either::Left(_) => None, + Either::Right(it) => it.name().map(|it| it.syntax().text_range()), + }; NavigationTarget { file_id: src.file_id.original_file(db), name: self.name(db).to_string().into(), kind: TYPE_PARAM, - full_range: range, - focus_range: None, + full_range, + focus_range, container_name: None, description: None, docs: None, diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs index 4379005aa645..8657377dedaa 100644 --- a/crates/ra_ide/src/folding_ranges.rs +++ b/crates/ra_ide/src/folding_ranges.rs @@ -88,7 +88,7 @@ fn fold_kind(kind: SyntaxKind) -> Option { | ITEM_LIST | EXTERN_ITEM_LIST | USE_TREE_LIST - | BLOCK + | BLOCK_EXPR | MATCH_ARM_LIST | ENUM_VARIANT_LIST | TOKEN_TREE => Some(FoldKind::Block), diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 1dfca819d567..90e85d419712 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs @@ -93,7 +93,7 @@ pub(crate) fn reference_definition( #[cfg(test)] mod tests { - use test_utils::{assert_eq_text, covers}; + use test_utils::assert_eq_text; use crate::mock_analysis::analysis_and_position; @@ -208,7 +208,6 @@ mod tests { #[test] fn goto_def_for_macros() { - covers!(ra_ide_db::goto_def_for_macros); check_goto( " //- /lib.rs @@ -225,7 +224,6 @@ mod tests { #[test] fn goto_def_for_macros_from_other_crates() { - covers!(ra_ide_db::goto_def_for_macros); check_goto( " //- /lib.rs @@ -243,6 +241,38 @@ mod tests { ); } + #[test] + fn goto_def_for_use_alias() { + check_goto( + " + //- /lib.rs + use foo as bar<|>; + + + //- /foo/lib.rs + #[macro_export] + macro_rules! foo { () => { () } }", + "SOURCE_FILE FileId(2) 0..50", + "#[macro_export]\nmacro_rules! foo { () => { () } }\n", + ); + } + + #[test] + fn goto_def_for_use_alias_foo_macro() { + check_goto( + " + //- /lib.rs + use foo::foo as bar<|>; + + //- /foo/lib.rs + #[macro_export] + macro_rules! foo { () => { () } } + ", + "foo MACRO_CALL FileId(2) 0..49 29..32", + "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", + ); + } + #[test] fn goto_def_for_macros_in_use_tree() { check_goto( @@ -337,7 +367,6 @@ mod tests { #[test] fn goto_def_for_methods() { - covers!(ra_ide_db::goto_def_for_methods); check_goto( " //- /lib.rs @@ -357,7 +386,6 @@ mod tests { #[test] fn goto_def_for_fields() { - covers!(ra_ide_db::goto_def_for_fields); check_goto( r" //- /lib.rs @@ -376,7 +404,6 @@ mod tests { #[test] fn goto_def_for_record_fields() { - covers!(ra_ide_db::goto_def_for_record_fields); check_goto( r" //- /lib.rs @@ -397,7 +424,6 @@ mod tests { #[test] fn goto_def_for_record_pat_fields() { - covers!(ra_ide_db::goto_def_for_record_field_pats); check_goto( r" //- /lib.rs @@ -754,14 +780,14 @@ mod tests { #[test] fn goto_for_type_param() { check_goto( - " + r#" //- /lib.rs - struct Foo { + struct Foo { t: <|>T, } - ", - "T TYPE_PARAM FileId(1) 11..12", - "T", + "#, + "T TYPE_PARAM FileId(1) 11..19 11..12", + "T: Clone|T", ); } @@ -840,7 +866,6 @@ mod tests { #[test] fn goto_def_for_field_init_shorthand() { - covers!(ra_ide_db::goto_def_for_field_init_shorthand); check_goto( " //- /lib.rs diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 54d318858227..befa977c7fe7 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option from_def_source(db, it, mod_path), ModuleDef::BuiltinType(it) => Some(it.to_string()), }, - Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), + Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))), Definition::TypeParam(_) | Definition::SelfType(_) => { // FIXME: Hover for generic param None @@ -208,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option { + a: A, + b: B, + c: C, + } + + struct FakeIter { + inner: I, + } + + struct OtherStruct { + i: T, + } + + enum FakeOption { + Some(T), + None, + } + + fn scan(a: A, b: B, c: C) -> FakeIter, B, C>> { + FakeIter { inner: Scan { a, b, c } } + } + + fn main() { + let num: i32 = 55; + let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption { + FakeOption::Some(*memo + value) + }; + let number = 5u32; + let mut iter<|> = scan(OtherStruct { i: num }, closure, number); + } + "#, + &["FakeIter>, |&mut u32, &u32, &mut u32| -> FakeOption, u32>>"], + ); + } + #[test] fn hover_shows_fn_signature() { // Single file with result @@ -405,7 +446,7 @@ mod tests { } #[test] - fn hover_omits_default_generic_types() { + fn hover_default_generic_types() { check_hover_result( r#" //- /main.rs @@ -417,7 +458,7 @@ struct Test { fn main() { let zz<|> = Test { t: 23, k: 33 }; }"#, - &["Test"], + &["Test"], ); } @@ -880,4 +921,21 @@ fn func(foo: i32) { if true { <|>foo; }; } &["unsafe trait foo"], ); } + + #[test] + fn test_hover_mod_with_same_name_as_function() { + check_hover_result( + " + //- /lib.rs + use self::m<|>y::Bar; + + mod my { + pub struct Bar; + } + + fn my() {} + ", + &["mod my"], + ); + } } diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 98483df32e46..b391f903a80a 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -9,6 +9,7 @@ use ra_syntax::{ }; use crate::{FileId, FunctionSignature}; +use stdx::to_lower_snake_case; #[derive(Clone, Debug, PartialEq, Eq)] pub struct InlayHintsConfig { @@ -144,7 +145,7 @@ fn get_param_name_hints( .iter() .skip(n_params_to_skip) .zip(args) - .filter(|(param, arg)| should_show_param_hint(&fn_signature, param, &arg)) + .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg)) .map(|(param_name, arg)| InlayHint { range: arg.syntax().text_range(), kind: InlayKind::ParameterHint, @@ -181,7 +182,7 @@ fn get_bind_pat_hints( fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { - let pat_text = bind_pat.syntax().to_string(); + let pat_text = bind_pat.to_string(); enum_data .variants(db) .into_iter() @@ -198,7 +199,7 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ } if let Some(Adt::Struct(s)) = pat_ty.as_adt() { - if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.syntax().to_string() { + if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { return true; } } @@ -230,15 +231,16 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ false } -fn should_show_param_hint( +fn should_show_param_name_hint( + sema: &Semantics, fn_signature: &FunctionSignature, param_name: &str, argument: &ast::Expr, ) -> bool { + let param_name = param_name.trim_start_matches('_'); if param_name.is_empty() - || is_argument_similar_to_param(argument, param_name) - || Some(param_name.trim_start_matches('_')) - == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) + || Some(param_name) == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) + || is_argument_similar_to_param_name(sema, argument, param_name) { return false; } @@ -254,20 +256,42 @@ fn should_show_param_hint( parameters_len != 1 || !is_obvious_param(param_name) } -fn is_argument_similar_to_param(argument: &ast::Expr, param_name: &str) -> bool { - let argument_string = remove_ref(argument.clone()).syntax().to_string(); - let param_name = param_name.trim_start_matches('_'); - let argument_string = argument_string.trim_start_matches('_'); - argument_string.starts_with(¶m_name) || argument_string.ends_with(¶m_name) -} - -fn remove_ref(expr: ast::Expr) -> ast::Expr { - if let ast::Expr::RefExpr(ref_expr) = &expr { - if let Some(inner) = ref_expr.expr() { - return inner; +fn is_argument_similar_to_param_name( + sema: &Semantics, + argument: &ast::Expr, + param_name: &str, +) -> bool { + if is_enum_name_similar_to_param_name(sema, argument, param_name) { + return true; + } + match get_string_representation(argument) { + None => false, + Some(repr) => { + let argument_string = repr.trim_start_matches('_'); + argument_string.starts_with(param_name) || argument_string.ends_with(param_name) } } - expr +} + +fn is_enum_name_similar_to_param_name( + sema: &Semantics, + argument: &ast::Expr, + param_name: &str, +) -> bool { + match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { + Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, + _ => false, + } +} + +fn get_string_representation(expr: &ast::Expr) -> Option { + match expr { + ast::Expr::MethodCallExpr(method_call_expr) => { + Some(method_call_expr.name_ref()?.to_string()) + } + ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), + _ => Some(expr.to_string()), + } } fn is_obvious_param(param_name: &str) -> bool { @@ -1073,6 +1097,12 @@ struct TestVarContainer { test_var: i32, } +impl TestVarContainer { + fn test_var(&self) -> i32 { + self.test_var + } +} + struct Test {} impl Test { @@ -1098,10 +1128,15 @@ struct Param {} fn different_order(param: &Param) {} fn different_order_mut(param: &mut Param) {} fn has_underscore(_param: bool) {} +fn enum_matches_param_name(completion_kind: CompletionKind) {} fn twiddle(twiddle: bool) {} fn doo(_doo: bool) {} +enum CompletionKind { + Keyword, +} + fn main() { let container: TestVarContainer = TestVarContainer { test_var: 42 }; let test: Test = Test {}; @@ -1114,18 +1149,21 @@ fn main() { let test_var: i32 = 55; test_processed.no_hints_expected(22, test_var); test_processed.no_hints_expected(33, container.test_var); + test_processed.no_hints_expected(44, container.test_var()); test_processed.frob(false); twiddle(true); doo(true); - let param_begin: Param = Param {}; + let mut param_begin: Param = Param {}; different_order(¶m_begin); different_order(&mut param_begin); let param: bool = true; has_underscore(param); + enum_matches_param_name(CompletionKind::Keyword); + let a: f64 = 7.0; let b: f64 = 4.0; let _: f64 = a.div_euclid(b); diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index d0def7eaafd0..af1ade8a1e12 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs @@ -129,8 +129,7 @@ fn has_comma_after(node: &SyntaxNode) -> bool { } fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { - let block = ast::Block::cast(token.parent())?; - let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?; + let block_expr = ast::BlockExpr::cast(token.parent())?; if !block_expr.is_standalone() { return None; } @@ -167,16 +166,28 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { #[cfg(test)] mod tests { - use crate::test_utils::{assert_eq_text, check_action, extract_range}; + use ra_syntax::SourceFile; + use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; use super::*; fn check_join_lines(before: &str, after: &str) { - check_action(before, after, |file, offset| { - let range = TextRange::empty(offset); - let res = join_lines(file, range); - Some(res) - }) + let (before_cursor_pos, before) = extract_offset(before); + let file = SourceFile::parse(&before).ok().unwrap(); + + let range = TextRange::empty(before_cursor_pos); + let result = join_lines(&file, range); + + let actual = { + let mut actual = before.to_string(); + result.apply(&mut actual); + actual + }; + let actual_cursor_pos = result + .apply_to_offset(before_cursor_pos) + .expect("cursor position is affected by the edit"); + let actual = add_cursor(&actual, actual_cursor_pos); + assert_eq_text!(after, &actual); } #[test] @@ -570,7 +581,11 @@ fn foo() { let (sel, before) = extract_range(before); let parse = SourceFile::parse(&before); let result = join_lines(&parse.tree(), sel); - let actual = result.apply(&before); + let actual = { + let mut actual = before.to_string(); + result.apply(&mut actual); + actual + }; assert_eq_text!(after, &actual); } diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 09f602fe1b22..97ff67ee891d 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -16,7 +16,6 @@ macro_rules! eprintln { } pub mod mock_analysis; -mod source_change; mod prime_caches; mod status; @@ -32,7 +31,6 @@ mod syntax_highlighting; mod parent_module; mod references; mod impls; -mod assists; mod diagnostics; mod syntax_tree; mod folding_ranges; @@ -44,11 +42,6 @@ mod inlay_hints; mod expand_macro; mod ssr; -#[cfg(test)] -mod marks; -#[cfg(test)] -mod test_utils; - use std::sync::Arc; use ra_cfg::CfgOptions; @@ -65,7 +58,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize}; use crate::display::ToNav; pub use crate::{ - assists::{Assist, AssistId}, call_hierarchy::CallItem, completion::{ CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, @@ -78,7 +70,6 @@ pub use crate::{ inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, runnables::{Runnable, RunnableKind, TestId}, - source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, ssr::SsrError, syntax_highlighting::{ Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, @@ -86,17 +77,19 @@ pub use crate::{ }; pub use hir::Documentation; +pub use ra_assists::{AssistConfig, AssistId}; pub use ra_db::{ Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, }; pub use ra_ide_db::{ change::{AnalysisChange, LibraryData}, line_index::{LineCol, LineIndex}, - line_index_utils::translate_offset_with_edit, search::SearchScope, + source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, symbol_index::Query, RootDatabase, }; +pub use ra_text_edit::{Indel, TextEdit}; pub type Cancelable = Result; @@ -135,10 +128,12 @@ pub struct AnalysisHost { db: RootDatabase, } -impl Default for AnalysisHost { - fn default() -> AnalysisHost { - AnalysisHost::new(None) - } +#[derive(Debug)] +pub struct Assist { + pub id: AssistId, + pub label: String, + pub group_label: Option, + pub source_change: SourceChange, } impl AnalysisHost { @@ -176,18 +171,20 @@ impl AnalysisHost { pub fn request_cancellation(&mut self) { self.db.request_cancellation(); } - pub fn raw_database( - &self, - ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { + pub fn raw_database(&self) -> &RootDatabase { &self.db } - pub fn raw_database_mut( - &mut self, - ) -> &mut (impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { + pub fn raw_database_mut(&mut self) -> &mut RootDatabase { &mut self.db } } +impl Default for AnalysisHost { + fn default() -> AnalysisHost { + AnalysisHost::new(None) + } +} + /// Analysis is a snapshot of a world state at a moment in time. It is the main /// entry point for asking semantic information about the world. When the world /// state is advanced using `AnalysisHost::apply_change` method, all existing @@ -289,14 +286,10 @@ impl Analysis { /// Returns an edit to remove all newlines in the range, cleaning up minor /// stuff like trailing commas. - pub fn join_lines(&self, frange: FileRange) -> Cancelable { + pub fn join_lines(&self, frange: FileRange) -> Cancelable { self.with_db(|db| { let parse = db.parse(frange.file_id); - let file_edit = SourceFileEdit { - file_id: frange.file_id, - edit: join_lines::join_lines(&parse.tree(), frange.range), - }; - SourceChange::source_file_edit("join lines", file_edit) + join_lines::join_lines(&parse.tree(), frange.range) }) } @@ -456,16 +449,26 @@ impl Analysis { /// Computes completions at the given position. pub fn completions( &self, - position: FilePosition, config: &CompletionConfig, + position: FilePosition, ) -> Cancelable>> { - self.with_db(|db| completion::completions(db, position, config).map(Into::into)) + self.with_db(|db| completion::completions(db, config, position).map(Into::into)) } /// Computes assists (aka code actions aka intentions) for the given /// position. - pub fn assists(&self, frange: FileRange) -> Cancelable> { - self.with_db(|db| assists::assists(db, frange)) + pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable> { + self.with_db(|db| { + ra_assists::Assist::resolved(db, config, frange) + .into_iter() + .map(|assist| Assist { + id: assist.assist.id, + label: assist.assist.label, + group_label: assist.assist.group.map(|it| it.0), + source_change: assist.source_change, + }) + .collect() + }) } /// Computes the set of diagnostics for the given file. @@ -490,7 +493,7 @@ impl Analysis { ) -> Cancelable> { self.with_db(|db| { let edits = ssr::parse_search_replace(query, parse_only, db)?; - Ok(SourceChange::source_file_edits("ssr", edits)) + Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) }) } diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs deleted file mode 100644 index bea30fe2af55..000000000000 --- a/crates/ra_ide/src/marks.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! See test_utils/src/marks.rs - -test_utils::marks!( - inserts_angle_brackets_for_generics - inserts_parens_for_function_calls - call_info_bad_offset - dont_complete_current_use - test_resolve_parent_module_on_module_decl - search_filters_by_range - dont_insert_macro_call_parens_unncessary - self_fulfilling_completion - test_struct_field_completion_in_func_call - test_struct_field_completion_in_record_lit -); diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index aaf4460dfa34..a083fb1eb358 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs @@ -7,7 +7,7 @@ use ra_syntax::{ algo::find_node_at_offset, ast::{self, AstNode}, }; -use test_utils::tested_by; +use test_utils::mark; use crate::NavigationTarget; @@ -25,7 +25,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec Vec { mod tests { use ra_cfg::CfgOptions; use ra_db::Env; - use test_utils::covers; + use test_utils::mark; use crate::{ mock_analysis::{analysis_and_position, MockAnalysis}, @@ -81,7 +81,7 @@ mod tests { #[test] fn test_resolve_parent_module_on_module_decl() { - covers!(test_resolve_parent_module_on_module_decl); + mark::check!(test_resolve_parent_module_on_module_decl); let (analysis, pos) = analysis_and_position( " //- /lib.rs diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 555ccf2952d7..96444bf6a52d 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -190,8 +190,6 @@ fn get_struct_def_name_for_struct_literal_search( #[cfg(test)] mod tests { - use test_utils::covers; - use crate::{ mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, Declaration, Reference, ReferenceSearchResult, SearchScope, @@ -301,7 +299,6 @@ mod tests { #[test] fn search_filters_by_range() { - covers!(ra_ide_db::search_filters_by_range); let code = r#" fn foo() { let spam<|> = 92; @@ -593,6 +590,31 @@ mod tests { check_result(refs, "i BIND_PAT FileId(1) 36..37 Other", &["FileId(1) 51..52 Other Write"]); } + #[test] + fn test_find_struct_function_refs_outside_module() { + let code = r#" + mod foo { + pub struct Foo; + + impl Foo { + pub fn new<|>() -> Foo { + Foo + } + } + } + + fn main() { + let _f = foo::Foo::new(); + }"#; + + let refs = get_all_refs(code); + check_result( + refs, + "new FN_DEF FileId(1) 87..150 94..97 Other", + &["FileId(1) 227..230 StructLiteral"], + ); + } + fn get_all_refs(text: &str) -> ReferenceSearchResult { let (analysis, position) = single_file_with_position(text); analysis.find_all_refs(position, None).unwrap().unwrap() diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index fd17bc9f2fef..fd2163dad663 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs @@ -4,17 +4,18 @@ use hir::{ModuleSource, Semantics}; use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; use ra_ide_db::RootDatabase; use ra_syntax::{ - algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, + algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, + AstNode, SyntaxKind, SyntaxNode, SyntaxToken, }; use ra_text_edit::TextEdit; +use std::convert::TryInto; +use test_utils::mark; use crate::{ - FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, - SourceFileEdit, TextRange, + references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, + SourceChange, SourceFileEdit, TextRange, TextSize, }; -use super::find_all_refs; - pub(crate) fn rename( db: &RootDatabase, position: FilePosition, @@ -22,17 +23,21 @@ pub(crate) fn rename( ) -> Option> { match lex_single_valid_syntax_kind(new_name)? { SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), + SyntaxKind::SELF_KW => return rename_to_self(db, position), _ => return None, } let sema = Semantics::new(db); let source_file = sema.parse(position.file_id); - if let Some((ast_name, ast_module)) = - find_name_and_module_at_offset(source_file.syntax(), position) - { + let syntax = source_file.syntax(); + if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { let range = ast_name.syntax().text_range(); rename_mod(&sema, &ast_name, &ast_module, position, new_name) .map(|info| RangeInfo::new(range, info)) + } else if let Some(self_token) = + syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) + { + rename_self_to_param(db, position, self_token, new_name) } else { rename_reference(sema.db, position, new_name) } @@ -52,11 +57,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil let file_id = reference.file_range.file_id; let range = match reference.kind { ReferenceKind::FieldShorthandForField => { + mark::hit!(test_rename_struct_field_for_shorthand); replacement_text.push_str(new_name); replacement_text.push_str(": "); TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) } ReferenceKind::FieldShorthandForLocal => { + mark::hit!(test_rename_local_for_field_shorthand); replacement_text.push_str(": "); replacement_text.push_str(new_name); TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) @@ -121,7 +128,113 @@ fn rename_mod( source_file_edits.extend(ref_edits); } - Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) + Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) +} + +fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syn = source_file.syntax(); + + let fn_def = find_node_at_offset::(syn, position.offset)?; + let params = fn_def.param_list()?; + if params.self_param().is_some() { + return None; // method already has self param + } + let first_param = params.params().next()?; + let mutable = match first_param.ascribed_type() { + Some(ast::TypeRef::ReferenceType(rt)) => rt.mut_token().is_some(), + _ => return None, // not renaming other types + }; + + let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; + + let param_range = first_param.syntax().text_range(); + let (param_ref, usages): (Vec, Vec) = refs + .into_iter() + .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); + + if param_ref.is_empty() { + return None; + } + + let mut edits = usages + .into_iter() + .map(|reference| source_edit_from_reference(reference, "self")) + .collect::>(); + + edits.push(SourceFileEdit { + file_id: position.file_id, + edit: TextEdit::replace( + param_range, + String::from(if mutable { "&mut self" } else { "&self" }), + ), + }); + + Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) +} + +fn text_edit_from_self_param( + syn: &SyntaxNode, + self_param: &ast::SelfParam, + new_name: &str, +) -> Option { + fn target_type_name(impl_def: &ast::ImplDef) -> Option { + if let Some(ast::TypeRef::PathType(p)) = impl_def.target_type() { + return Some(p.path()?.segment()?.name_ref()?.text().to_string()); + } + None + } + + let impl_def = + find_node_at_offset::(syn, self_param.syntax().text_range().start())?; + let type_name = target_type_name(&impl_def)?; + + let mut replacement_text = String::from(new_name); + replacement_text.push_str(": "); + replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut ")); + replacement_text.push_str(type_name.as_str()); + + Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) +} + +fn rename_self_to_param( + db: &RootDatabase, + position: FilePosition, + self_token: SyntaxToken, + new_name: &str, +) -> Option> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syn = source_file.syntax(); + + let text = db.file_text(position.file_id); + let fn_def = find_node_at_offset::(syn, position.offset)?; + let search_range = fn_def.syntax().text_range(); + + let mut edits: Vec = vec![]; + + for (idx, _) in text.match_indices("self") { + let offset: TextSize = idx.try_into().unwrap(); + if !search_range.contains_inclusive(offset) { + continue; + } + if let Some(ref usage) = + syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) + { + let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { + text_edit_from_self_param(syn, self_param, new_name)? + } else { + TextEdit::replace(usage.text_range(), String::from(new_name)) + }; + edits.push(SourceFileEdit { file_id: position.file_id, edit }); + } + } + + let range = ast::SelfParam::cast(self_token.parent()) + .map_or(self_token.text_range(), |p| p.syntax().text_range()); + + Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) } fn rename_reference( @@ -140,14 +253,14 @@ fn rename_reference( return None; } - Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) + Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) } #[cfg(test)] mod tests { use insta::assert_debug_snapshot; use ra_text_edit::TextEditBuilder; - use test_utils::assert_eq_text; + use test_utils::{assert_eq_text, mark}; use crate::{ mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, @@ -379,6 +492,7 @@ mod tests { #[test] fn test_rename_struct_field_for_shorthand() { + mark::check!(test_rename_struct_field_for_shorthand); test_rename( r#" struct Foo { @@ -408,6 +522,7 @@ mod tests { #[test] fn test_rename_local_for_field_shorthand() { + mark::check!(test_rename_local_for_field_shorthand); test_rename( r#" struct Foo { @@ -527,17 +642,17 @@ mod tests { RangeInfo { range: 4..7, info: SourceChange { - label: "rename", + label: "Rename", source_file_edits: [ SourceFileEdit { file_id: FileId( 2, ), edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: 4..7, + indels: [ + Indel { insert: "foo2", + delete: 4..7, }, ], }, @@ -554,7 +669,7 @@ mod tests { dst_path: "bar/foo2.rs", }, ], - cursor_position: None, + is_snippet: false, }, }, ) @@ -579,17 +694,17 @@ mod tests { RangeInfo { range: 4..7, info: SourceChange { - label: "rename", + label: "Rename", source_file_edits: [ SourceFileEdit { file_id: FileId( 1, ), edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: 4..7, + indels: [ + Indel { insert: "foo2", + delete: 4..7, }, ], }, @@ -606,7 +721,7 @@ mod tests { dst_path: "foo2/mod.rs", }, ], - cursor_position: None, + is_snippet: false, }, }, ) @@ -662,17 +777,17 @@ mod tests { RangeInfo { range: 8..11, info: SourceChange { - label: "rename", + label: "Rename", source_file_edits: [ SourceFileEdit { file_id: FileId( 2, ), edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: 8..11, + indels: [ + Indel { insert: "foo2", + delete: 8..11, }, ], }, @@ -682,10 +797,10 @@ mod tests { 1, ), edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: 27..30, + indels: [ + Indel { insert: "foo2", + delete: 27..30, }, ], }, @@ -702,13 +817,164 @@ mod tests { dst_path: "bar/foo2.rs", }, ], - cursor_position: None, + is_snippet: false, }, }, ) "###); } + #[test] + fn test_enum_variant_from_module_1() { + test_rename( + r#" + mod foo { + pub enum Foo { + Bar<|>, + } + } + + fn func(f: foo::Foo) { + match f { + foo::Foo::Bar => {} + } + } + "#, + "Baz", + r#" + mod foo { + pub enum Foo { + Baz, + } + } + + fn func(f: foo::Foo) { + match f { + foo::Foo::Baz => {} + } + } + "#, + ); + } + + #[test] + fn test_enum_variant_from_module_2() { + test_rename( + r#" + mod foo { + pub struct Foo { + pub bar<|>: uint, + } + } + + fn foo(f: foo::Foo) { + let _ = f.bar; + } + "#, + "baz", + r#" + mod foo { + pub struct Foo { + pub baz: uint, + } + } + + fn foo(f: foo::Foo) { + let _ = f.baz; + } + "#, + ); + } + + #[test] + fn test_parameter_to_self() { + test_rename( + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(foo<|>: &mut Foo) -> i32 { + foo.i + } + } + "#, + "self", + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(&mut self) -> i32 { + self.i + } + } + "#, + ); + } + + #[test] + fn test_self_to_parameter() { + test_rename( + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(&mut <|>self) -> i32 { + self.i + } + } + "#, + "foo", + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(foo: &mut Foo) -> i32 { + foo.i + } + } + "#, + ); + } + + #[test] + fn test_self_in_path_to_parameter() { + test_rename( + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(&self) -> i32 { + let self_var = 1; + self<|>.i + } + } + "#, + "foo", + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(foo: &Foo) -> i32 { + let self_var = 1; + foo.i + } + } + "#, + ); + } + fn test_rename(text: &str, new_name: &str, expected: &str) { let (analysis, position) = single_file_with_position(text); let source_change = analysis.rename(position, new_name).unwrap(); @@ -717,13 +983,13 @@ mod tests { if let Some(change) = source_change { for edit in change.info.source_file_edits { file_id = Some(edit.file_id); - for atom in edit.edit.as_atoms() { - text_edit_builder.replace(atom.delete, atom.insert.clone()); + for indel in edit.edit.into_iter() { + text_edit_builder.replace(indel.delete, indel.insert); } } } - let result = - text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); + let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); + text_edit_builder.finish().apply(&mut result); assert_eq_text!(expected, &*result); } } diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 38637c19c203..131b8f307c13 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use hir::Semantics; +use hir::{AsAssocItem, Semantics}; use itertools::Itertools; use ra_ide_db::RootDatabase; use ra_syntax::{ @@ -9,6 +9,7 @@ use ra_syntax::{ }; use crate::FileId; +use ast::DocCommentsOwner; use std::fmt::Display; #[derive(Debug)] @@ -37,6 +38,7 @@ pub enum RunnableKind { Test { test_id: TestId, attr: TestAttr }, TestMod { path: String }, Bench { test_id: TestId }, + DocTest { test_id: TestId }, Bin, } @@ -63,14 +65,36 @@ fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option { + Some(trait_item.name(sema.db).to_string()) + } + hir::AssocItemContainer::ImplDef(impl_def) => impl_def + .target_ty(sema.db) + .as_adt() + .map(|adt| adt.name(sema.db).to_string()), + } + }); + + let path_iter = module .path_to_root(sema.db) .into_iter() .rev() .filter_map(|it| it.name(sema.db)) - .map(|name| name.to_string()) - .chain(std::iter::once(name_string)) - .join("::"); + .map(|name| name.to_string()); + + let path = if let Some(impl_trait_name) = impl_trait_name { + path_iter + .chain(std::iter::once(impl_trait_name)) + .chain(std::iter::once(name_string)) + .join("::") + } else { + path_iter.chain(std::iter::once(name_string)).join("::") + }; + TestId::Path(path) } else { TestId::Name(name_string) @@ -81,6 +105,8 @@ fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option bool { .any(|attribute_text| attribute_text.contains("test")) } +fn has_doc_test(fn_def: &ast::FnDef) -> bool { + fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) +} + fn runnable_mod(sema: &Semantics, module: ast::Module) -> Option { let has_test_function = module .item_list()? @@ -194,6 +224,79 @@ mod tests { ); } + #[test] + fn test_runnables_doc_test() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + fn main() {} + + /// ``` + /// let x = 5; + /// ``` + fn foo() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: 1..21, + kind: Bin, + }, + Runnable { + range: 22..64, + kind: DocTest { + test_id: Path( + "foo", + ), + }, + }, + ] + "### + ); + } + + #[test] + fn test_runnables_doc_test_in_impl() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + fn main() {} + + struct Data; + impl Data { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: 1..21, + kind: Bin, + }, + Runnable { + range: 51..105, + kind: DocTest { + test_id: Path( + "Data::foo", + ), + }, + }, + ] + "### + ); + } + #[test] fn test_runnables_module() { let (analysis, pos) = analysis_and_position( diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index de06daf72b08..752b487e82fa 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html @@ -27,13 +27,13 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .keyword.unsafe { color: #BC8383; font-weight: bold; } .control { font-style: italic; } -
macro_rules! println {
+
macro_rules! println {
     ($($arg:tt)*) => ({
         $crate::io::_print($crate::format_args_nl!($($arg)*));
     })
 }
 #[rustc_builtin_macro]
-macro_rules! format_args_nl {
+macro_rules! format_args_nl {
     ($fmt:expr) => {{ /* compiler built-in */ }};
     ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
 }
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 4b12fe8238cf..635fe5cf9ddf 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -33,11 +33,23 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     pub y: i32,
 }
 
+trait Bar {
+    fn bar(&self) -> i32;
+}
+
+impl Bar for Foo {
+    fn bar(&self) -> i32 {
+        self.x
+    }
+}
+
+static mut STATIC_MUT: i32 = 0;
+
 fn foo<'a, T>() -> T {
     foo::<'a, i32>()
 }
 
-macro_rules! def_fn {
+macro_rules! def_fn {
     ($($tt:tt)*) => {$($tt)*}
 }
 
@@ -56,7 +68,14 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
         let x = 92;
         vec.push(Foo { x, y: 1 });
     }
-    unsafe { vec.set_len(0); }
+    unsafe {
+        vec.set_len(0);
+        STATIC_MUT = 1;
+    }
+
+    for e in vec {
+        // Do nothing
+    }
 
     let mut x = 42;
     let y = &mut x;
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 7b93ff2d28eb..1873d1d0d0ab 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,18 +1,18 @@
 //!  structural search replace
 
-use crate::source_change::SourceFileEdit;
+use std::{collections::HashMap, iter::once, str::FromStr};
+
 use ra_db::{SourceDatabase, SourceDatabaseExt};
-use ra_ide_db::symbol_index::SymbolsDatabase;
-use ra_ide_db::RootDatabase;
-use ra_syntax::ast::make::try_expr_from_text;
+use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
 use ra_syntax::ast::{
-    ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, RecordField, RecordLit,
+    make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr,
+    RecordField, RecordLit,
 };
 use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode};
 use ra_text_edit::{TextEdit, TextEditBuilder};
 use rustc_hash::FxHashMap;
-use std::collections::HashMap;
-use std::{iter::once, str::FromStr};
+
+use crate::SourceFileEdit;
 
 #[derive(Debug, PartialEq)]
 pub struct SsrError(String);
@@ -401,16 +401,22 @@ fn render_replace(
     ignored_comments: &Vec,
     template: &SsrTemplate,
 ) -> String {
-    let mut builder = TextEditBuilder::default();
-    for element in template.template.descendants() {
-        if let Some(var) = template.placeholders.get(&element) {
-            builder.replace(element.text_range(), binding[var].to_string())
+    let edit = {
+        let mut builder = TextEditBuilder::default();
+        for element in template.template.descendants() {
+            if let Some(var) = template.placeholders.get(&element) {
+                builder.replace(element.text_range(), binding[var].to_string())
+            }
         }
-    }
-    for comment in ignored_comments {
-        builder.insert(template.template.text_range().end(), comment.syntax().to_string())
-    }
-    builder.finish().apply(&template.template.text().to_string())
+        for comment in ignored_comments {
+            builder.insert(template.template.text_range().end(), comment.syntax().to_string())
+        }
+        builder.finish()
+    };
+
+    let mut text = template.template.text().to_string();
+    edit.apply(&mut text);
+    text
 }
 
 #[cfg(test)]
@@ -505,7 +511,9 @@ mod tests {
         );
 
         let edit = replace(&matches, &query.template);
-        assert_eq!(edit.apply(input), "fn main() { bar(1+2); }");
+        let mut after = input.to_string();
+        edit.apply(&mut after);
+        assert_eq!(after, "fn main() { bar(1+2); }");
     }
 
     fn assert_ssr_transform(query: &str, input: &str, result: &str) {
@@ -513,7 +521,9 @@ mod tests {
         let code = SourceFile::parse(input).tree();
         let matches = find(&query.pattern, code.syntax());
         let edit = replace(&matches, &query.template);
-        assert_eq!(edit.apply(input), result);
+        let mut after = input.to_string();
+        edit.apply(&mut after);
+        assert_eq!(after, result);
     }
 
     #[test]
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 6658c7bb270e..be57eeb0abea 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -167,6 +167,19 @@ pub(crate) fn highlight(
                         binding_hash: None,
                     });
                 }
+                if let Some(name) = mc.is_macro_rules() {
+                    if let Some((highlight, binding_hash)) = highlight_element(
+                        &sema,
+                        &mut bindings_shadow_count,
+                        name.syntax().clone().into(),
+                    ) {
+                        stack.add(HighlightedRange {
+                            range: name.syntax().text_range(),
+                            highlight,
+                            binding_hash,
+                        });
+                    }
+                }
                 continue;
             }
             WalkEvent::Leave(Some(mc)) => {
@@ -390,12 +403,13 @@ fn highlight_element(
                 T![break]
                 | T![continue]
                 | T![else]
-                | T![for]
                 | T![if]
                 | T![loop]
                 | T![match]
                 | T![return]
-                | T![while] => h | HighlightModifier::ControlFlow,
+                | T![while]
+                | T![in] => h | HighlightModifier::ControlFlow,
+                T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
                 T![unsafe] => h | HighlightModifier::Unsafe,
                 _ => h,
             }
@@ -419,6 +433,13 @@ fn highlight_element(
     }
 }
 
+fn is_child_of_impl(element: SyntaxElement) -> bool {
+    match element.parent() {
+        Some(e) => e.kind() == IMPL_DEF,
+        _ => false,
+    }
+}
+
 fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
     match def {
         Definition::Macro(_) => HighlightTag::Macro,
@@ -431,10 +452,16 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
             hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
             hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant,
             hir::ModuleDef::Const(_) => HighlightTag::Constant,
-            hir::ModuleDef::Static(_) => HighlightTag::Static,
             hir::ModuleDef::Trait(_) => HighlightTag::Trait,
             hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias,
             hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType,
+            hir::ModuleDef::Static(s) => {
+                let mut h = Highlight::new(HighlightTag::Static);
+                if s.is_mut(db) {
+                    h |= HighlightModifier::Mutable;
+                }
+                return h;
+            }
         },
         Definition::SelfType(_) => HighlightTag::SelfType,
         Definition::TypeParam(_) => HighlightTag::TypeParam,
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index d2926ba78029..eb43a23da682 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -17,6 +17,18 @@ struct Foo {
     pub y: i32,
 }
 
+trait Bar {
+    fn bar(&self) -> i32;
+}
+
+impl Bar for Foo {
+    fn bar(&self) -> i32 {
+        self.x
+    }
+}
+
+static mut STATIC_MUT: i32 = 0;
+
 fn foo<'a, T>() -> T {
     foo::<'a, i32>()
 }
@@ -40,7 +52,14 @@ fn main() {
         let x = 92;
         vec.push(Foo { x, y: 1 });
     }
-    unsafe { vec.set_len(0); }
+    unsafe {
+        vec.set_len(0);
+        STATIC_MUT = 1;
+    }
+
+    for e in vec {
+        // Do nothing
+    }
 
     let mut x = 42;
     let y = &mut x;
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs
index bf97f8c569c8..86c70ff830b8 100644
--- a/crates/ra_ide/src/syntax_tree.rs
+++ b/crates/ra_ide/src/syntax_tree.rs
@@ -120,9 +120,8 @@ SOURCE_FILE@0..11
       R_PAREN@7..8 ")"
     WHITESPACE@8..9 " "
     BLOCK_EXPR@9..11
-      BLOCK@9..11
-        L_CURLY@9..10 "{"
-        R_CURLY@10..11 "}"
+      L_CURLY@9..10 "{"
+      R_CURLY@10..11 "}"
 "#
             .trim()
         );
@@ -153,26 +152,25 @@ SOURCE_FILE@0..60
       R_PAREN@8..9 ")"
     WHITESPACE@9..10 " "
     BLOCK_EXPR@10..60
-      BLOCK@10..60
-        L_CURLY@10..11 "{"
-        WHITESPACE@11..16 "\n    "
-        EXPR_STMT@16..58
-          MACRO_CALL@16..57
-            PATH@16..22
-              PATH_SEGMENT@16..22
-                NAME_REF@16..22
-                  IDENT@16..22 "assert"
-            BANG@22..23 "!"
-            TOKEN_TREE@23..57
-              L_PAREN@23..24 "("
-              STRING@24..52 "\"\n    fn foo() {\n     ..."
-              COMMA@52..53 ","
-              WHITESPACE@53..54 " "
-              STRING@54..56 "\"\""
-              R_PAREN@56..57 ")"
-          SEMICOLON@57..58 ";"
-        WHITESPACE@58..59 "\n"
-        R_CURLY@59..60 "}"
+      L_CURLY@10..11 "{"
+      WHITESPACE@11..16 "\n    "
+      EXPR_STMT@16..58
+        MACRO_CALL@16..57
+          PATH@16..22
+            PATH_SEGMENT@16..22
+              NAME_REF@16..22
+                IDENT@16..22 "assert"
+          BANG@22..23 "!"
+          TOKEN_TREE@23..57
+            L_PAREN@23..24 "("
+            STRING@24..52 "\"\n    fn foo() {\n     ..."
+            COMMA@52..53 ","
+            WHITESPACE@53..54 " "
+            STRING@54..56 "\"\""
+            R_PAREN@56..57 ")"
+        SEMICOLON@57..58 ";"
+      WHITESPACE@58..59 "\n"
+      R_CURLY@59..60 "}"
 "#
             .trim()
         );
@@ -196,9 +194,8 @@ FN_DEF@0..11
     R_PAREN@7..8 ")"
   WHITESPACE@8..9 " "
   BLOCK_EXPR@9..11
-    BLOCK@9..11
-      L_CURLY@9..10 "{"
-      R_CURLY@10..11 "}"
+    L_CURLY@9..10 "{"
+    R_CURLY@10..11 "}"
 "#
             .trim()
         );
@@ -265,10 +262,9 @@ SOURCE_FILE@0..12
       R_PAREN@7..8 ")"
     WHITESPACE@8..9 " "
     BLOCK_EXPR@9..12
-      BLOCK@9..12
-        L_CURLY@9..10 "{"
-        WHITESPACE@10..11 "\n"
-        R_CURLY@11..12 "}"
+      L_CURLY@9..10 "{"
+      WHITESPACE@10..11 "\n"
+      R_CURLY@11..12 "}"
 "#
             .trim()
         );
@@ -300,10 +296,9 @@ SOURCE_FILE@0..12
       R_PAREN@7..8 ")"
     WHITESPACE@8..9 " "
     BLOCK_EXPR@9..12
-      BLOCK@9..12
-        L_CURLY@9..10 "{"
-        WHITESPACE@10..11 "\n"
-        R_CURLY@11..12 "}"
+      L_CURLY@9..10 "{"
+      WHITESPACE@10..11 "\n"
+      R_CURLY@11..12 "}"
 "#
             .trim()
         );
@@ -334,10 +329,9 @@ SOURCE_FILE@0..25
       R_PAREN@7..8 ")"
     WHITESPACE@8..9 " "
     BLOCK_EXPR@9..12
-      BLOCK@9..12
-        L_CURLY@9..10 "{"
-        WHITESPACE@10..11 "\n"
-        R_CURLY@11..12 "}"
+      L_CURLY@9..10 "{"
+      WHITESPACE@10..11 "\n"
+      R_CURLY@11..12 "}"
   WHITESPACE@12..13 "\n"
   FN_DEF@13..25
     FN_KW@13..15 "fn"
@@ -349,10 +343,9 @@ SOURCE_FILE@0..25
       R_PAREN@20..21 ")"
     WHITESPACE@21..22 " "
     BLOCK_EXPR@22..25
-      BLOCK@22..25
-        L_CURLY@22..23 "{"
-        WHITESPACE@23..24 "\n"
-        R_CURLY@24..25 "}"
+      L_CURLY@22..23 "{"
+      WHITESPACE@23..24 "\n"
+      R_CURLY@24..25 "}"
 "#
             .trim()
         );
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
deleted file mode 100644
index f14533e14bb7..000000000000
--- a/crates/ra_ide/src/test_utils.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-//! FIXME: write short doc here
-
-use ra_syntax::{SourceFile, TextSize};
-use ra_text_edit::TextEdit;
-
-pub use test_utils::*;
-
-pub fn check_action Option>(
-    before: &str,
-    after: &str,
-    f: F,
-) {
-    let (before_cursor_pos, before) = extract_offset(before);
-    let file = SourceFile::parse(&before).ok().unwrap();
-    let result = f(&file, before_cursor_pos).expect("code action is not applicable");
-    let actual = result.apply(&before);
-    let actual_cursor_pos =
-        result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
-    let actual = add_cursor(&actual, actual_cursor_pos);
-    assert_eq_text!(after, &actual);
-}
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 2a8b4327f9c9..cd48cad93b96 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -17,15 +17,16 @@ mod on_enter;
 
 use ra_db::{FilePosition, SourceDatabase};
 use ra_fmt::leading_indent;
-use ra_ide_db::RootDatabase;
+use ra_ide_db::{source_change::SingleFileChange, RootDatabase};
 use ra_syntax::{
     algo::find_node_at_offset,
     ast::{self, AstToken},
     AstNode, SourceFile, TextRange, TextSize,
 };
+
 use ra_text_edit::TextEdit;
 
-use crate::{source_change::SingleFileChange, SourceChange};
+use crate::SourceChange;
 
 pub(crate) use on_enter::on_enter;
 
@@ -81,7 +82,6 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option
     Some(SingleFileChange {
         label: "add semicolon".to_string(),
         edit: TextEdit::insert(offset, ";".to_string()),
-        cursor_position: None,
     })
 }
 
@@ -110,7 +110,6 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option
     Some(SingleFileChange {
         label: "reindent dot".to_string(),
         edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
-        cursor_position: Some(offset + target_indent_len - current_indent_len + TextSize::of('.')),
     })
 }
 
@@ -129,7 +128,6 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option Option<(String, SingleFileChange)> {
+    fn do_type_char(char_typed: char, before: &str) -> Option {
         let (offset, before) = extract_offset(before);
         let edit = TextEdit::insert(offset, char_typed.to_string());
-        let before = edit.apply(&before);
+        let mut before = before.to_string();
+        edit.apply(&mut before);
         let parse = SourceFile::parse(&before);
-        on_char_typed_inner(&parse.tree(), offset, char_typed)
-            .map(|it| (it.edit.apply(&before), it))
+        on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
+            it.edit.apply(&mut before);
+            before.to_string()
+        })
     }
 
     fn type_char(char_typed: char, before: &str, after: &str) {
-        let (actual, file_change) = do_type_char(char_typed, before)
+        let actual = do_type_char(char_typed, before)
             .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
 
-        if after.contains("<|>") {
-            let (offset, after) = extract_offset(after);
-            assert_eq_text!(&after, &actual);
-            assert_eq!(file_change.cursor_position, Some(offset))
-        } else {
-            assert_eq_text!(after, &actual);
-        }
+        assert_eq_text!(after, &actual);
     }
 
     fn type_char_noop(char_typed: char, before: &str) {
@@ -346,6 +341,6 @@ fn foo() {
 
     #[test]
     fn adds_space_after_return_type() {
-        type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }")
+        type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }")
     }
 }
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 30c8c557201e..85be14ad33a2 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -38,17 +38,15 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option bool {
@@ -84,7 +82,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option {
 
 #[cfg(test)]
 mod tests {
-    use test_utils::{add_cursor, assert_eq_text, extract_offset};
+    use test_utils::{assert_eq_text, extract_offset};
 
     use crate::mock_analysis::single_file;
 
@@ -96,8 +94,8 @@ mod tests {
         let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
 
         assert_eq!(result.source_file_edits.len(), 1);
-        let actual = result.source_file_edits[0].edit.apply(&before);
-        let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
+        let mut actual = before.to_string();
+        result.source_file_edits[0].edit.apply(&mut actual);
         Some(actual)
     }
 
@@ -120,7 +118,7 @@ fn foo() {
 ",
             r"
 /// Some docs
-/// <|>
+/// $0
 fn foo() {
 }
 ",
@@ -136,7 +134,7 @@ impl S {
             r"
 impl S {
     /// Some
-    /// <|> docs.
+    /// $0 docs.
     fn foo() {}
 }
 ",
@@ -150,7 +148,7 @@ fn foo() {
 ",
             r"
 ///
-/// <|> Some docs
+/// $0 Some docs
 fn foo() {
 }
 ",
@@ -174,7 +172,7 @@ fn main() {
             r"
 fn main() {
     // Fix
-    // <|> me
+    // $0 me
     let x = 1 + 1;
 }
 ",
@@ -194,7 +192,7 @@ fn main() {
             r"
 fn main() {
     // Fix
-    // <|>
+    // $0
     // me
     let x = 1 + 1;
 }
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index 7cd2384e9b5f..8b06cbfc54b7 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -14,7 +14,6 @@ use ra_syntax::{
     ast::{self, AstNode},
     match_ast,
 };
-use test_utils::tested_by;
 
 use crate::RootDatabase;
 
@@ -42,12 +41,10 @@ impl Definition {
     }
 
     pub fn visibility(&self, db: &RootDatabase) -> Option {
-        let module = self.module(db);
-
         match self {
             Definition::Macro(_) => None,
             Definition::Field(sf) => Some(sf.visibility(db)),
-            Definition::ModuleDef(def) => module?.visibility_of(db, def),
+            Definition::ModuleDef(def) => def.definition_visibility(db),
             Definition::SelfType(_) => None,
             Definition::Local(_) => None,
             Definition::TypeParam(_) => None,
@@ -119,6 +116,15 @@ fn classify_name_inner(sema: &Semantics, name: &ast::Name) -> Opti
 
     match_ast! {
         match parent {
+            ast::Alias(it) => {
+                let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?;
+                let path = use_tree.path()?;
+                let path_segment = path.segment()?;
+                let name_ref = path_segment.name_ref()?;
+                let name_ref_class = classify_name_ref(sema, &name_ref)?;
+
+                Some(name_ref_class.definition())
+            },
             ast::BindPat(it) => {
                 let local = sema.to_def(&it)?;
                 Some(Definition::Local(local))
@@ -195,6 +201,8 @@ impl NameRefClass {
     }
 }
 
+// Note: we don't have unit-tests for this rather important function.
+// It is primarily exercised via goto definition tests in `ra_ide`.
 pub fn classify_name_ref(
     sema: &Semantics,
     name_ref: &ast::NameRef,
@@ -204,22 +212,18 @@ pub fn classify_name_ref(
     let parent = name_ref.syntax().parent()?;
 
     if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
-        tested_by!(goto_def_for_methods; force);
         if let Some(func) = sema.resolve_method_call(&method_call) {
             return Some(NameRefClass::Definition(Definition::ModuleDef(func.into())));
         }
     }
 
     if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
-        tested_by!(goto_def_for_fields; force);
         if let Some(field) = sema.resolve_field(&field_expr) {
             return Some(NameRefClass::Definition(Definition::Field(field)));
         }
     }
 
     if let Some(record_field) = ast::RecordField::for_field_name(name_ref) {
-        tested_by!(goto_def_for_record_fields; force);
-        tested_by!(goto_def_for_field_init_shorthand; force);
         if let Some((field, local)) = sema.resolve_record_field(&record_field) {
             let field = Definition::Field(field);
             let res = match local {
@@ -231,7 +235,6 @@ pub fn classify_name_ref(
     }
 
     if let Some(record_field_pat) = ast::RecordFieldPat::cast(parent.clone()) {
-        tested_by!(goto_def_for_record_field_pats; force);
         if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
             let field = Definition::Field(field);
             return Some(NameRefClass::Definition(field));
@@ -239,7 +242,6 @@ pub fn classify_name_ref(
     }
 
     if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
-        tested_by!(goto_def_for_macros; force);
         if let Some(macro_def) = sema.resolve_macro_call(¯o_call) {
             return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
         }
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index e6f2d36e9463..1b74e6558613 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -2,14 +2,13 @@
 //!
 //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
 
-pub mod marks;
 pub mod line_index;
-pub mod line_index_utils;
 pub mod symbol_index;
 pub mod change;
 pub mod defs;
 pub mod search;
 pub mod imports_locator;
+pub mod source_change;
 mod wasm_shims;
 
 use std::sync::Arc;
diff --git a/crates/ra_ide_db/src/line_index.rs b/crates/ra_ide_db/src/line_index.rs
index 00ba95913c6a..c7c744fce1ef 100644
--- a/crates/ra_ide_db/src/line_index.rs
+++ b/crates/ra_ide_db/src/line_index.rs
@@ -8,7 +8,9 @@ use superslice::Ext;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct LineIndex {
+    /// Offset the the beginning of each line, zero-based
     pub(crate) newlines: Vec,
+    /// List of non-ASCII characters on each line
     pub(crate) utf16_lines: FxHashMap>,
 }
 
@@ -22,14 +24,26 @@ pub struct LineCol {
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub(crate) struct Utf16Char {
+    /// Start offset of a character inside a line, zero-based
     pub(crate) start: TextSize,
+    /// End offset of a character inside a line, zero-based
     pub(crate) end: TextSize,
 }
 
 impl Utf16Char {
+    /// Returns the length in 8-bit UTF-8 code units.
     fn len(&self) -> TextSize {
         self.end - self.start
     }
+
+    /// Returns the length in 16-bit UTF-16 code units.
+    fn len_utf16(&self) -> usize {
+        if self.len() == TextSize::from(4) {
+            2
+        } else {
+            1
+        }
+    }
 }
 
 impl LineIndex {
@@ -106,7 +120,7 @@ impl LineIndex {
         if let Some(utf16_chars) = self.utf16_lines.get(&line) {
             for c in utf16_chars {
                 if c.end <= col {
-                    res -= usize::from(c.len()) - 1;
+                    res -= usize::from(c.len()) - c.len_utf16();
                 } else {
                     // From here on, all utf16 characters come *after* the character we are mapping,
                     // so we don't need to take them into account
@@ -120,8 +134,8 @@ impl LineIndex {
     fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
         if let Some(utf16_chars) = self.utf16_lines.get(&line) {
             for c in utf16_chars {
-                if col >= u32::from(c.start) {
-                    col += u32::from(c.len()) - 1;
+                if col > u32::from(c.start) {
+                    col += u32::from(c.len()) - c.len_utf16() as u32;
                 } else {
                     // From here on, all utf16 characters come *after* the character we are mapping,
                     // so we don't need to take them into account
@@ -200,6 +214,9 @@ const C: char = 'メ';
 
         // UTF-16 to UTF-8
         assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21));
+
+        let col_index = LineIndex::new("a𐐏b");
+        assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5));
     }
 
     #[test]
@@ -226,8 +243,10 @@ const C: char = \"メ メ\";
         // UTF-16 to UTF-8
         assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
 
-        assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20));
-        assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(23));
+        // メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1
+        assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20
+        assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space
+        assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24
 
         assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15));
     }
diff --git a/crates/ra_ide_db/src/line_index_utils.rs b/crates/ra_ide_db/src/line_index_utils.rs
deleted file mode 100644
index 039a12c0d848..000000000000
--- a/crates/ra_ide_db/src/line_index_utils.rs
+++ /dev/null
@@ -1,302 +0,0 @@
-//! Code actions can specify desirable final position of the cursor.
-//!
-//! The position is specified as a `TextSize` in the final file. We need to send
-//! it in `(Line, Column)` coordinate though. However, we only have a LineIndex
-//! for a file pre-edit!
-//!
-//! Code in this module applies this "to (Line, Column) after edit"
-//! transformation.
-
-use std::convert::TryInto;
-
-use ra_syntax::{TextRange, TextSize};
-use ra_text_edit::{AtomTextEdit, TextEdit};
-
-use crate::line_index::{LineCol, LineIndex, Utf16Char};
-
-pub fn translate_offset_with_edit(
-    line_index: &LineIndex,
-    offset: TextSize,
-    text_edit: &TextEdit,
-) -> LineCol {
-    let mut state = Edits::from_text_edit(&text_edit);
-
-    let mut res = RunningLineCol::new();
-
-    macro_rules! test_step {
-        ($x:ident) => {
-            match &$x {
-                Step::Newline(n) => {
-                    if offset < *n {
-                        return res.to_line_col(offset);
-                    } else {
-                        res.add_line(*n);
-                    }
-                }
-                Step::Utf16Char(x) => {
-                    if offset < x.end() {
-                        // if the offset is inside a multibyte char it's invalid
-                        // clamp it to the start of the char
-                        let clamp = offset.min(x.start());
-                        return res.to_line_col(clamp);
-                    } else {
-                        res.adjust_col(*x);
-                    }
-                }
-            }
-        };
-    }
-
-    for orig_step in LineIndexStepIter::from(line_index) {
-        loop {
-            let translated_step = state.translate_step(&orig_step);
-            match state.next_steps(&translated_step) {
-                NextSteps::Use => {
-                    test_step!(translated_step);
-                    break;
-                }
-                NextSteps::ReplaceMany(ns) => {
-                    for n in ns {
-                        test_step!(n);
-                    }
-                    break;
-                }
-                NextSteps::AddMany(ns) => {
-                    for n in ns {
-                        test_step!(n);
-                    }
-                }
-            }
-        }
-    }
-
-    loop {
-        match state.next_inserted_steps() {
-            None => break,
-            Some(ns) => {
-                for n in ns {
-                    test_step!(n);
-                }
-            }
-        }
-    }
-
-    res.to_line_col(offset)
-}
-
-#[derive(Debug, Clone)]
-enum Step {
-    Newline(TextSize),
-    Utf16Char(TextRange),
-}
-
-#[derive(Debug)]
-struct LineIndexStepIter<'a> {
-    line_index: &'a LineIndex,
-    next_newline_idx: usize,
-    utf16_chars: Option<(TextSize, std::slice::Iter<'a, Utf16Char>)>,
-}
-
-impl LineIndexStepIter<'_> {
-    fn from(line_index: &LineIndex) -> LineIndexStepIter {
-        let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None };
-        // skip first newline since it's not real
-        x.next();
-        x
-    }
-}
-
-impl Iterator for LineIndexStepIter<'_> {
-    type Item = Step;
-    fn next(&mut self) -> Option {
-        self.utf16_chars
-            .as_mut()
-            .and_then(|(newline, x)| {
-                let x = x.next()?;
-                Some(Step::Utf16Char(TextRange::new(*newline + x.start, *newline + x.end)))
-            })
-            .or_else(|| {
-                let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?;
-                self.utf16_chars = self
-                    .line_index
-                    .utf16_lines
-                    .get(&(self.next_newline_idx as u32))
-                    .map(|x| (next_newline, x.iter()));
-                self.next_newline_idx += 1;
-                Some(Step::Newline(next_newline))
-            })
-    }
-}
-
-#[derive(Debug)]
-struct OffsetStepIter<'a> {
-    text: &'a str,
-    offset: TextSize,
-}
-
-impl Iterator for OffsetStepIter<'_> {
-    type Item = Step;
-    fn next(&mut self) -> Option {
-        let (next, next_offset) = self
-            .text
-            .char_indices()
-            .filter_map(|(i, c)| {
-                let i: TextSize = i.try_into().unwrap();
-                let char_len = TextSize::of(c);
-                if c == '\n' {
-                    let next_offset = self.offset + i + char_len;
-                    let next = Step::Newline(next_offset);
-                    Some((next, next_offset))
-                } else {
-                    if !c.is_ascii() {
-                        let start = self.offset + i;
-                        let end = start + char_len;
-                        let next = Step::Utf16Char(TextRange::new(start, end));
-                        let next_offset = end;
-                        Some((next, next_offset))
-                    } else {
-                        None
-                    }
-                }
-            })
-            .next()?;
-        let next_idx: usize = (next_offset - self.offset).into();
-        self.text = &self.text[next_idx..];
-        self.offset = next_offset;
-        Some(next)
-    }
-}
-
-#[derive(Debug)]
-enum NextSteps<'a> {
-    Use,
-    ReplaceMany(OffsetStepIter<'a>),
-    AddMany(OffsetStepIter<'a>),
-}
-
-#[derive(Debug)]
-struct TranslatedEdit<'a> {
-    delete: TextRange,
-    insert: &'a str,
-    diff: i64,
-}
-
-struct Edits<'a> {
-    edits: &'a [AtomTextEdit],
-    current: Option>,
-    acc_diff: i64,
-}
-
-impl<'a> Edits<'a> {
-    fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
-        let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 };
-        x.advance_edit();
-        x
-    }
-    fn advance_edit(&mut self) {
-        self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff);
-        match self.edits.split_first() {
-            Some((next, rest)) => {
-                let delete = self.translate_range(next.delete);
-                let diff = next.insert.len() as i64 - usize::from(next.delete.len()) as i64;
-                self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff });
-                self.edits = rest;
-            }
-            None => {
-                self.current = None;
-            }
-        }
-    }
-
-    fn next_inserted_steps(&mut self) -> Option> {
-        let cur = self.current.as_ref()?;
-        let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert });
-        self.advance_edit();
-        res
-    }
-
-    fn next_steps(&mut self, step: &Step) -> NextSteps {
-        let step_pos = match *step {
-            Step::Newline(n) => n,
-            Step::Utf16Char(r) => r.end(),
-        };
-        match &mut self.current {
-            Some(edit) => {
-                if step_pos <= edit.delete.start() {
-                    NextSteps::Use
-                } else if step_pos <= edit.delete.end() {
-                    let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
-                    // empty slice to avoid returning steps again
-                    edit.insert = &edit.insert[edit.insert.len()..];
-                    NextSteps::ReplaceMany(iter)
-                } else {
-                    let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
-                    // empty slice to avoid returning steps again
-                    edit.insert = &edit.insert[edit.insert.len()..];
-                    self.advance_edit();
-                    NextSteps::AddMany(iter)
-                }
-            }
-            None => NextSteps::Use,
-        }
-    }
-
-    fn translate_range(&self, range: TextRange) -> TextRange {
-        if self.acc_diff == 0 {
-            range
-        } else {
-            let start = self.translate(range.start());
-            let end = self.translate(range.end());
-            TextRange::new(start, end)
-        }
-    }
-
-    fn translate(&self, x: TextSize) -> TextSize {
-        if self.acc_diff == 0 {
-            x
-        } else {
-            TextSize::from((usize::from(x) as i64 + self.acc_diff) as u32)
-        }
-    }
-
-    fn translate_step(&self, x: &Step) -> Step {
-        if self.acc_diff == 0 {
-            x.clone()
-        } else {
-            match *x {
-                Step::Newline(n) => Step::Newline(self.translate(n)),
-                Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)),
-            }
-        }
-    }
-}
-
-#[derive(Debug)]
-struct RunningLineCol {
-    line: u32,
-    last_newline: TextSize,
-    col_adjust: TextSize,
-}
-
-impl RunningLineCol {
-    fn new() -> RunningLineCol {
-        RunningLineCol { line: 0, last_newline: TextSize::from(0), col_adjust: TextSize::from(0) }
-    }
-
-    fn to_line_col(&self, offset: TextSize) -> LineCol {
-        LineCol {
-            line: self.line,
-            col_utf16: ((offset - self.last_newline) - self.col_adjust).into(),
-        }
-    }
-
-    fn add_line(&mut self, newline: TextSize) {
-        self.line += 1;
-        self.last_newline = newline;
-        self.col_adjust = TextSize::from(0);
-    }
-
-    fn adjust_col(&mut self, range: TextRange) {
-        self.col_adjust += range.len() - TextSize::from(1);
-    }
-}
diff --git a/crates/ra_ide_db/src/marks.rs b/crates/ra_ide_db/src/marks.rs
deleted file mode 100644
index 03b4be21c974..000000000000
--- a/crates/ra_ide_db/src/marks.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//! See test_utils/src/marks.rs
-
-test_utils::marks![
-    goto_def_for_macros
-    goto_def_for_methods
-    goto_def_for_fields
-    goto_def_for_record_fields
-    goto_def_for_field_init_shorthand
-    goto_def_for_record_field_pats
-    search_filters_by_range
-];
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs
index b464959fce0e..589f44771926 100644
--- a/crates/ra_ide_db/src/search.rs
+++ b/crates/ra_ide_db/src/search.rs
@@ -12,7 +12,6 @@ use ra_db::{FileId, FileRange, SourceDatabaseExt};
 use ra_prof::profile;
 use ra_syntax::{ast, match_ast, AstNode, TextRange, TextSize};
 use rustc_hash::FxHashMap;
-use test_utils::tested_by;
 
 use crate::{
     defs::{classify_name_ref, Definition, NameRefClass},
@@ -209,7 +208,6 @@ impl Definition {
             for (idx, _) in text.match_indices(pat) {
                 let offset: TextSize = idx.try_into().unwrap();
                 if !search_range.contains_inclusive(offset) {
-                    tested_by!(search_filters_by_range; force);
                     continue;
                 }
 
diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs
similarity index 63%
rename from crates/ra_ide/src/source_change.rs
rename to crates/ra_ide_db/src/source_change.rs
index 71b0e8f757ed..3484f55886b8 100644
--- a/crates/ra_ide/src/source_change.rs
+++ b/crates/ra_ide_db/src/source_change.rs
@@ -3,23 +3,22 @@
 //!
 //! It can be viewed as a dual for `AnalysisChange`.
 
-use ra_db::RelativePathBuf;
+use ra_db::{FileId, RelativePathBuf, SourceRootId};
 use ra_text_edit::TextEdit;
 
-use crate::{FileId, FilePosition, SourceRootId, TextSize};
-
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct SourceChange {
+    /// For display in the undo log in the editor
     pub label: String,
     pub source_file_edits: Vec,
     pub file_system_edits: Vec,
-    pub cursor_position: Option,
+    pub is_snippet: bool,
 }
 
 impl SourceChange {
     /// Creates a new SourceChange with the given label
     /// from the edits.
-    pub(crate) fn from_edits>(
+    pub fn from_edits>(
         label: L,
         source_file_edits: Vec,
         file_system_edits: Vec,
@@ -28,18 +27,20 @@ impl SourceChange {
             label: label.into(),
             source_file_edits,
             file_system_edits,
-            cursor_position: None,
+            is_snippet: false,
         }
     }
 
     /// Creates a new SourceChange with the given label,
     /// containing only the given `SourceFileEdits`.
-    pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self {
+    pub fn source_file_edits>(label: L, edits: Vec) -> Self {
+        let label = label.into();
+        assert!(label.starts_with(char::is_uppercase));
         SourceChange {
-            label: label.into(),
+            label: label,
             source_file_edits: edits,
             file_system_edits: vec![],
-            cursor_position: None,
+            is_snippet: false,
         }
     }
 
@@ -50,19 +51,19 @@ impl SourceChange {
             label: label.into(),
             source_file_edits: vec![],
             file_system_edits: edits,
-            cursor_position: None,
+            is_snippet: false,
         }
     }
 
     /// Creates a new SourceChange with the given label,
     /// containing only a single `SourceFileEdit`.
-    pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self {
+    pub fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self {
         SourceChange::source_file_edits(label, vec![edit])
     }
 
     /// Creates a new SourceChange with the given label
     /// from the given `FileId` and `TextEdit`
-    pub(crate) fn source_file_edit_from>(
+    pub fn source_file_edit_from>(
         label: L,
         file_id: FileId,
         edit: TextEdit,
@@ -72,48 +73,35 @@ impl SourceChange {
 
     /// Creates a new SourceChange with the given label
     /// from the given `FileId` and `TextEdit`
-    pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self {
+    pub fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self {
         SourceChange::file_system_edits(label, vec![edit])
     }
-
-    /// Sets the cursor position to the given `FilePosition`
-    pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
-        self.cursor_position = Some(cursor_position);
-        self
-    }
-
-    /// Sets the cursor position to the given `FilePosition`
-    pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self {
-        self.cursor_position = cursor_position;
-        self
-    }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct SourceFileEdit {
     pub file_id: FileId,
     pub edit: TextEdit,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum FileSystemEdit {
     CreateFile { source_root: SourceRootId, path: RelativePathBuf },
     MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
 }
 
-pub(crate) struct SingleFileChange {
+pub struct SingleFileChange {
     pub label: String,
     pub edit: TextEdit,
-    pub cursor_position: Option,
 }
 
 impl SingleFileChange {
-    pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange {
+    pub fn into_source_change(self, file_id: FileId) -> SourceChange {
         SourceChange {
             label: self.label,
             source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
             file_system_edits: Vec::new(),
-            cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }),
+            is_snippet: false,
         }
     }
 }
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs
index 1a020398e9ea..9c450eabaed0 100644
--- a/crates/ra_mbe/src/lib.rs
+++ b/crates/ra_mbe/src/lib.rs
@@ -22,7 +22,7 @@ pub enum ParseError {
     RepetitionEmtpyTokenTree,
 }
 
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 pub enum ExpandError {
     NoMatchingRule,
     UnexpectedToken,
diff --git a/crates/ra_mbe/src/mbe_expander/transcriber.rs b/crates/ra_mbe/src/mbe_expander/transcriber.rs
index 4b173edd3f91..7c9bb4d00e1a 100644
--- a/crates/ra_mbe/src/mbe_expander/transcriber.rs
+++ b/crates/ra_mbe/src/mbe_expander/transcriber.rs
@@ -1,4 +1,4 @@
-//! Transcraber takes a template, like `fn $ident() {}`, a set of bindings like
+//! Transcriber takes a template, like `fn $ident() {}`, a set of bindings like
 //! `$ident => foo`, interpolates variables in the template, to get `fn foo() {}`
 
 use ra_syntax::SmolStr;
@@ -53,7 +53,8 @@ impl Bindings {
 pub(super) fn transcribe(template: &tt::Subtree, bindings: &Bindings) -> ExpandResult {
     assert!(template.delimiter == None);
     let mut ctx = ExpandCtx { bindings: &bindings, nesting: Vec::new() };
-    expand_subtree(&mut ctx, template)
+    let mut arena: Vec = Vec::new();
+    expand_subtree(&mut ctx, template, &mut arena)
 }
 
 #[derive(Debug)]
@@ -73,8 +74,13 @@ struct ExpandCtx<'a> {
     nesting: Vec,
 }
 
-fn expand_subtree(ctx: &mut ExpandCtx, template: &tt::Subtree) -> ExpandResult {
-    let mut buf: Vec = Vec::new();
+fn expand_subtree(
+    ctx: &mut ExpandCtx,
+    template: &tt::Subtree,
+    arena: &mut Vec,
+) -> ExpandResult {
+    // remember how many elements are in the arena now - when returning, we want to drain exactly how many elements we added. This way, the recursive uses of the arena get their own "view" of the arena, but will reuse the allocation
+    let start_elements = arena.len();
     let mut err = None;
     for op in parse_template(template) {
         let op = match op {
@@ -85,25 +91,27 @@ fn expand_subtree(ctx: &mut ExpandCtx, template: &tt::Subtree) -> ExpandResult buf.push(tt.clone()),
+            Op::TokenTree(tt @ tt::TokenTree::Leaf(..)) => arena.push(tt.clone()),
             Op::TokenTree(tt::TokenTree::Subtree(tt)) => {
-                let ExpandResult(tt, e) = expand_subtree(ctx, tt);
+                let ExpandResult(tt, e) = expand_subtree(ctx, tt, arena);
                 err = err.or(e);
-                buf.push(tt.into());
+                arena.push(tt.into());
             }
             Op::Var { name, kind: _ } => {
                 let ExpandResult(fragment, e) = expand_var(ctx, name);
                 err = err.or(e);
-                push_fragment(&mut buf, fragment);
+                push_fragment(arena, fragment);
             }
             Op::Repeat { subtree, kind, separator } => {
-                let ExpandResult(fragment, e) = expand_repeat(ctx, subtree, kind, separator);
+                let ExpandResult(fragment, e) = expand_repeat(ctx, subtree, kind, separator, arena);
                 err = err.or(e);
-                push_fragment(&mut buf, fragment)
+                push_fragment(arena, fragment)
             }
         }
     }
-    ExpandResult(tt::Subtree { delimiter: template.delimiter, token_trees: buf }, err)
+    // drain the elements added in this instance of expand_subtree
+    let tts = arena.drain(start_elements..arena.len()).collect();
+    ExpandResult(tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err)
 }
 
 fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult {
@@ -155,6 +163,7 @@ fn expand_repeat(
     template: &tt::Subtree,
     kind: RepeatKind,
     separator: Option,
+    arena: &mut Vec,
 ) -> ExpandResult {
     let mut buf: Vec = Vec::new();
     ctx.nesting.push(NestingState { idx: 0, at_end: false, hit: false });
@@ -165,7 +174,7 @@ fn expand_repeat(
     let mut counter = 0;
 
     loop {
-        let ExpandResult(mut t, e) = expand_subtree(ctx, template);
+        let ExpandResult(mut t, e) = expand_subtree(ctx, template, arena);
         let nesting_state = ctx.nesting.last_mut().unwrap();
         if nesting_state.at_end || !nesting_state.hit {
             break;
diff --git a/crates/ra_mbe/src/syntax_bridge.rs b/crates/ra_mbe/src/syntax_bridge.rs
index bb28acfd95d8..fc4133a67534 100644
--- a/crates/ra_mbe/src/syntax_bridge.rs
+++ b/crates/ra_mbe/src/syntax_bridge.rs
@@ -63,7 +63,7 @@ pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> Option<(tt::Subtree, Toke
 // * Items(SmallVec<[P; 1]>)     -> token_tree_to_items
 //
 // * TraitItems(SmallVec<[ast::TraitItem; 1]>)
-// * ImplItems(SmallVec<[ast::ImplItem; 1]>)
+// * AssocItems(SmallVec<[ast::AssocItem; 1]>)
 // * ForeignItems(SmallVec<[ast::ForeignItem; 1]>
 
 pub fn token_tree_to_syntax_node(
diff --git a/crates/ra_mbe/src/tests.rs b/crates/ra_mbe/src/tests.rs
index 0d924ce582fd..c43003fd63df 100644
--- a/crates/ra_mbe/src/tests.rs
+++ b/crates/ra_mbe/src/tests.rs
@@ -266,21 +266,20 @@ fn test_expr_order() {
       L_PAREN@5..6 "("
       R_PAREN@6..7 ")"
     BLOCK_EXPR@7..15
-      BLOCK@7..15
-        L_CURLY@7..8 "{"
-        EXPR_STMT@8..14
-          BIN_EXPR@8..13
-            BIN_EXPR@8..11
-              LITERAL@8..9
-                INT_NUMBER@8..9 "1"
-              PLUS@9..10 "+"
-              LITERAL@10..11
-                INT_NUMBER@10..11 "1"
-            STAR@11..12 "*"
-            LITERAL@12..13
-              INT_NUMBER@12..13 "2"
-          SEMICOLON@13..14 ";"
-        R_CURLY@14..15 "}""#,
+      L_CURLY@7..8 "{"
+      EXPR_STMT@8..14
+        BIN_EXPR@8..13
+          BIN_EXPR@8..11
+            LITERAL@8..9
+              INT_NUMBER@8..9 "1"
+            PLUS@9..10 "+"
+            LITERAL@10..11
+              INT_NUMBER@10..11 "1"
+          STAR@11..12 "*"
+          LITERAL@12..13
+            INT_NUMBER@12..13 "2"
+        SEMICOLON@13..14 ";"
+      R_CURLY@14..15 "}""#,
     );
 }
 
@@ -1114,68 +1113,67 @@ fn test_vec() {
     assert_eq!(
         format!("{:#?}", tree).trim(),
         r#"BLOCK_EXPR@0..45
-  BLOCK@0..45
-    L_CURLY@0..1 "{"
-    LET_STMT@1..20
-      LET_KW@1..4 "let"
-      BIND_PAT@4..8
-        MUT_KW@4..7 "mut"
-        NAME@7..8
-          IDENT@7..8 "v"
-      EQ@8..9 "="
-      CALL_EXPR@9..19
-        PATH_EXPR@9..17
-          PATH@9..17
-            PATH@9..12
-              PATH_SEGMENT@9..12
-                NAME_REF@9..12
-                  IDENT@9..12 "Vec"
-            COLON2@12..14 "::"
-            PATH_SEGMENT@14..17
-              NAME_REF@14..17
-                IDENT@14..17 "new"
-        ARG_LIST@17..19
-          L_PAREN@17..18 "("
-          R_PAREN@18..19 ")"
-      SEMICOLON@19..20 ";"
-    EXPR_STMT@20..33
-      METHOD_CALL_EXPR@20..32
-        PATH_EXPR@20..21
-          PATH@20..21
-            PATH_SEGMENT@20..21
-              NAME_REF@20..21
-                IDENT@20..21 "v"
-        DOT@21..22 "."
-        NAME_REF@22..26
-          IDENT@22..26 "push"
-        ARG_LIST@26..32
-          L_PAREN@26..27 "("
-          LITERAL@27..31
-            INT_NUMBER@27..31 "1u32"
-          R_PAREN@31..32 ")"
-      SEMICOLON@32..33 ";"
-    EXPR_STMT@33..43
-      METHOD_CALL_EXPR@33..42
-        PATH_EXPR@33..34
-          PATH@33..34
-            PATH_SEGMENT@33..34
-              NAME_REF@33..34
-                IDENT@33..34 "v"
-        DOT@34..35 "."
-        NAME_REF@35..39
-          IDENT@35..39 "push"
-        ARG_LIST@39..42
-          L_PAREN@39..40 "("
-          LITERAL@40..41
-            INT_NUMBER@40..41 "2"
-          R_PAREN@41..42 ")"
-      SEMICOLON@42..43 ";"
-    PATH_EXPR@43..44
-      PATH@43..44
-        PATH_SEGMENT@43..44
-          NAME_REF@43..44
-            IDENT@43..44 "v"
-    R_CURLY@44..45 "}""#
+  L_CURLY@0..1 "{"
+  LET_STMT@1..20
+    LET_KW@1..4 "let"
+    BIND_PAT@4..8
+      MUT_KW@4..7 "mut"
+      NAME@7..8
+        IDENT@7..8 "v"
+    EQ@8..9 "="
+    CALL_EXPR@9..19
+      PATH_EXPR@9..17
+        PATH@9..17
+          PATH@9..12
+            PATH_SEGMENT@9..12
+              NAME_REF@9..12
+                IDENT@9..12 "Vec"
+          COLON2@12..14 "::"
+          PATH_SEGMENT@14..17
+            NAME_REF@14..17
+              IDENT@14..17 "new"
+      ARG_LIST@17..19
+        L_PAREN@17..18 "("
+        R_PAREN@18..19 ")"
+    SEMICOLON@19..20 ";"
+  EXPR_STMT@20..33
+    METHOD_CALL_EXPR@20..32
+      PATH_EXPR@20..21
+        PATH@20..21
+          PATH_SEGMENT@20..21
+            NAME_REF@20..21
+              IDENT@20..21 "v"
+      DOT@21..22 "."
+      NAME_REF@22..26
+        IDENT@22..26 "push"
+      ARG_LIST@26..32
+        L_PAREN@26..27 "("
+        LITERAL@27..31
+          INT_NUMBER@27..31 "1u32"
+        R_PAREN@31..32 ")"
+    SEMICOLON@32..33 ";"
+  EXPR_STMT@33..43
+    METHOD_CALL_EXPR@33..42
+      PATH_EXPR@33..34
+        PATH@33..34
+          PATH_SEGMENT@33..34
+            NAME_REF@33..34
+              IDENT@33..34 "v"
+      DOT@34..35 "."
+      NAME_REF@35..39
+        IDENT@35..39 "push"
+      ARG_LIST@39..42
+        L_PAREN@39..40 "("
+        LITERAL@40..41
+          INT_NUMBER@40..41 "2"
+        R_PAREN@41..42 ")"
+    SEMICOLON@42..43 ";"
+  PATH_EXPR@43..44
+    PATH@43..44
+      PATH_SEGMENT@43..44
+        NAME_REF@43..44
+          IDENT@43..44 "v"
+  R_CURLY@44..45 "}""#
     );
 }
 
diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs
index c2a6e82e9b45..be0cd5661bd6 100644
--- a/crates/ra_parser/src/grammar.rs
+++ b/crates/ra_parser/src/grammar.rs
@@ -54,7 +54,7 @@ pub(crate) mod fragments {
     use super::*;
 
     pub(crate) use super::{
-        expressions::block, paths::type_path as path, patterns::pattern, types::type_,
+        expressions::block_expr, paths::type_path as path, patterns::pattern, types::type_,
     };
 
     pub(crate) fn expr(p: &mut Parser) {
@@ -143,7 +143,7 @@ pub(crate) fn reparser(
     parent: Option,
 ) -> Option {
     let res = match node {
-        BLOCK => expressions::naked_block,
+        BLOCK_EXPR => expressions::block_expr,
         RECORD_FIELD_DEF_LIST => items::record_field_def_list,
         RECORD_FIELD_LIST => items::record_field_list,
         ENUM_VARIANT_LIST => items::enum_variant_list,
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index cb30b25a890e..34f0397686fb 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -2,7 +2,7 @@
 
 mod atom;
 
-pub(crate) use self::atom::match_arm_list;
+pub(crate) use self::atom::{block_expr, match_arm_list};
 pub(super) use self::atom::{literal, LITERAL_FIRST};
 use super::*;
 
@@ -49,28 +49,6 @@ fn expr_no_struct(p: &mut Parser) {
     expr_bp(p, r, 1);
 }
 
-// test block
-// fn a() {}
-// fn b() { let _ = 1; }
-// fn c() { 1; 2; }
-// fn d() { 1; 2 }
-pub(crate) fn block(p: &mut Parser) {
-    if !p.at(T!['{']) {
-        p.error("expected a block");
-        return;
-    }
-    atom::block_expr(p, None);
-}
-
-pub(crate) fn naked_block(p: &mut Parser) {
-    assert!(p.at(T!['{']));
-    let m = p.start();
-    p.bump(T!['{']);
-    expr_block_contents(p);
-    p.expect(T!['}']);
-    m.complete(p, BLOCK);
-}
-
 fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool {
     match kind {
         BIN_EXPR | RANGE_EXPR | IF_EXPR => false,
@@ -197,7 +175,7 @@ pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi) {
     }
 }
 
-pub(crate) fn expr_block_contents(p: &mut Parser) {
+pub(super) fn expr_block_contents(p: &mut Parser) {
     // This is checked by a validator
     attributes::inner_attributes(p);
 
diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs
index 76aa601cb56c..706a2f796b21 100644
--- a/crates/ra_parser/src/grammar/expressions/atom.rs
+++ b/crates/ra_parser/src/grammar/expressions/atom.rs
@@ -84,7 +84,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
         T![box] => box_expr(p, None),
         T![for] => for_expr(p, None),
         T![while] => while_expr(p, None),
-        T![try] => try_expr(p, None),
+        T![try] => try_block_expr(p, None),
         LIFETIME if la == T![:] => {
             let m = p.start();
             label(p);
@@ -92,7 +92,12 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
                 T![loop] => loop_expr(p, Some(m)),
                 T![for] => for_expr(p, Some(m)),
                 T![while] => while_expr(p, Some(m)),
-                T!['{'] => block_expr(p, Some(m)),
+                // test labeled_block
+                // fn f() { 'label: {}; }
+                T!['{'] => {
+                    block_expr(p);
+                    m.complete(p, EFFECT_EXPR)
+                }
                 _ => {
                     // test_err misplaced_label_err
                     // fn main() {
@@ -108,13 +113,17 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
             let m = p.start();
             p.bump(T![async]);
             p.eat(T![move]);
-            block_expr(p, Some(m))
+            block_expr(p);
+            m.complete(p, EFFECT_EXPR)
         }
         T![match] => match_expr(p),
+        // test unsafe_block
+        // fn f() { unsafe { } }
         T![unsafe] if la == T!['{'] => {
             let m = p.start();
             p.bump(T![unsafe]);
-            block_expr(p, Some(m))
+            block_expr(p);
+            m.complete(p, EFFECT_EXPR)
         }
         T!['{'] => {
             // test for_range_from
@@ -123,7 +132,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
             //        break;
             //    }
             // }
-            block_expr(p, None)
+            block_expr_unchecked(p)
         }
         T![return] => return_expr(p),
         T![continue] => continue_expr(p),
@@ -134,7 +143,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
         }
     };
     let blocklike = match done.kind() {
-        IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR | BLOCK_EXPR | TRY_EXPR => {
+        IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR | BLOCK_EXPR | EFFECT_EXPR => {
             BlockLike::Block
         }
         _ => BlockLike::NotBlock,
@@ -231,13 +240,9 @@ fn lambda_expr(p: &mut Parser) -> CompletedMarker {
     p.eat(T![move]);
     params::param_list_closure(p);
     if opt_fn_ret_type(p) {
-        if p.at(T!['{']) {
-            // test lambda_ret_block
-            // fn main() { || -> i32 { 92 }(); }
-            block_expr(p, None);
-        } else {
-            p.error("expected `{`");
-        }
+        // test lambda_ret_block
+        // fn main() { || -> i32 { 92 }(); }
+        block_expr(p);
     } else {
         if p.at_ts(EXPR_FIRST) {
             expr(p);
@@ -261,13 +266,13 @@ fn if_expr(p: &mut Parser) -> CompletedMarker {
     let m = p.start();
     p.bump(T![if]);
     cond(p);
-    block(p);
+    block_expr(p);
     if p.at(T![else]) {
         p.bump(T![else]);
         if p.at(T![if]) {
             if_expr(p);
         } else {
-            block(p);
+            block_expr(p);
         }
     }
     m.complete(p, IF_EXPR)
@@ -295,7 +300,7 @@ fn loop_expr(p: &mut Parser, m: Option) -> CompletedMarker {
     assert!(p.at(T![loop]));
     let m = m.unwrap_or_else(|| p.start());
     p.bump(T![loop]);
-    block(p);
+    block_expr(p);
     m.complete(p, LOOP_EXPR)
 }
 
@@ -310,7 +315,7 @@ fn while_expr(p: &mut Parser, m: Option) -> CompletedMarker {
     let m = m.unwrap_or_else(|| p.start());
     p.bump(T![while]);
     cond(p);
-    block(p);
+    block_expr(p);
     m.complete(p, WHILE_EXPR)
 }
 
@@ -325,7 +330,7 @@ fn for_expr(p: &mut Parser, m: Option) -> CompletedMarker {
     patterns::pattern(p);
     p.expect(T![in]);
     expr_no_struct(p);
-    block(p);
+    block_expr(p);
     m.complete(p, FOR_EXPR)
 }
 
@@ -458,16 +463,25 @@ fn match_guard(p: &mut Parser) -> CompletedMarker {
     m.complete(p, MATCH_GUARD)
 }
 
-// test block_expr
-// fn foo() {
-//     {};
-//     unsafe {};
-//     'label: {};
-// }
-pub(super) fn block_expr(p: &mut Parser, m: Option) -> CompletedMarker {
+// test block
+// fn a() {}
+// fn b() { let _ = 1; }
+// fn c() { 1; 2; }
+// fn d() { 1; 2 }
+pub(crate) fn block_expr(p: &mut Parser) {
+    if !p.at(T!['{']) {
+        p.error("expected a block");
+        return;
+    }
+    block_expr_unchecked(p);
+}
+
+fn block_expr_unchecked(p: &mut Parser) -> CompletedMarker {
     assert!(p.at(T!['{']));
-    let m = m.unwrap_or_else(|| p.start());
-    naked_block(p);
+    let m = p.start();
+    p.bump(T!['{']);
+    expr_block_contents(p);
+    p.expect(T!['}']);
     m.complete(p, BLOCK_EXPR)
 }
 
@@ -532,7 +546,7 @@ fn break_expr(p: &mut Parser, r: Restrictions) -> CompletedMarker {
 // fn foo() {
 //     let _ = try {};
 // }
-fn try_expr(p: &mut Parser, m: Option) -> CompletedMarker {
+fn try_block_expr(p: &mut Parser, m: Option) -> CompletedMarker {
     assert!(p.at(T![try]));
     let m = m.unwrap_or_else(|| p.start());
     // Special-case `try!` as macro.
@@ -552,8 +566,8 @@ fn try_expr(p: &mut Parser, m: Option) -> CompletedMarker {
     }
 
     p.bump(T![try]);
-    block(p);
-    m.complete(p, TRY_EXPR)
+    block_expr(p);
+    m.complete(p, EFFECT_EXPR)
 }
 
 // test box_expr
diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs
index 1503a87300fa..67a924de5397 100644
--- a/crates/ra_parser/src/grammar/items.rs
+++ b/crates/ra_parser/src/grammar/items.rs
@@ -329,7 +329,7 @@ fn fn_def(p: &mut Parser) {
     if p.at(T![;]) {
         p.bump(T![;]);
     } else {
-        expressions::block(p)
+        expressions::block_expr(p)
     }
 }
 
diff --git a/crates/ra_parser/src/grammar/type_args.rs b/crates/ra_parser/src/grammar/type_args.rs
index 33d9973e9920..2d61f9d8083a 100644
--- a/crates/ra_parser/src/grammar/type_args.rs
+++ b/crates/ra_parser/src/grammar/type_args.rs
@@ -48,7 +48,7 @@ fn type_arg(p: &mut Parser) {
             m.complete(p, ASSOC_TYPE_ARG);
         }
         T!['{'] => {
-            expressions::block(p);
+            expressions::block_expr(p);
             m.complete(p, CONST_ARG);
         }
         k if k.is_literal() => {
diff --git a/crates/ra_parser/src/lib.rs b/crates/ra_parser/src/lib.rs
index 652492c1ed96..eeb8ad66bd16 100644
--- a/crates/ra_parser/src/lib.rs
+++ b/crates/ra_parser/src/lib.rs
@@ -25,7 +25,7 @@ pub(crate) use token_set::TokenSet;
 pub use syntax_kind::SyntaxKind;
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ParseError(pub String);
+pub struct ParseError(pub Box);
 
 /// `TokenSource` abstracts the source of the tokens parser operates on.
 ///
@@ -112,7 +112,7 @@ pub fn parse_fragment(
         FragmentKind::Type => grammar::fragments::type_,
         FragmentKind::Pattern => grammar::fragments::pattern,
         FragmentKind::Item => grammar::fragments::item,
-        FragmentKind::Block => grammar::fragments::block,
+        FragmentKind::Block => grammar::fragments::block_expr,
         FragmentKind::Visibility => grammar::fragments::opt_visibility,
         FragmentKind::MetaItem => grammar::fragments::meta_item,
         FragmentKind::Statement => grammar::fragments::stmt,
diff --git a/crates/ra_parser/src/parser.rs b/crates/ra_parser/src/parser.rs
index faa63d53f2c2..4f59b0a2356f 100644
--- a/crates/ra_parser/src/parser.rs
+++ b/crates/ra_parser/src/parser.rs
@@ -192,7 +192,7 @@ impl<'t> Parser<'t> {
     /// structured errors with spans and notes, like rustc
     /// does.
     pub(crate) fn error>(&mut self, message: T) {
-        let msg = ParseError(message.into());
+        let msg = ParseError(Box::new(message.into()));
         self.push_event(Event::Error { msg })
     }
 
diff --git a/crates/ra_parser/src/syntax_kind/generated.rs b/crates/ra_parser/src/syntax_kind/generated.rs
index ab727ed7e18d..e7404492a8e9 100644
--- a/crates/ra_parser/src/syntax_kind/generated.rs
+++ b/crates/ra_parser/src/syntax_kind/generated.rs
@@ -191,6 +191,7 @@ pub enum SyntaxKind {
     RECORD_LIT,
     RECORD_FIELD_LIST,
     RECORD_FIELD,
+    EFFECT_EXPR,
     BOX_EXPR,
     CALL_EXPR,
     INDEX_EXPR,
@@ -203,7 +204,6 @@ pub enum SyntaxKind {
     PREFIX_EXPR,
     RANGE_EXPR,
     BIN_EXPR,
-    BLOCK,
     EXTERN_BLOCK,
     EXTERN_ITEM_LIST,
     ENUM_VARIANT,
diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml
index 886e148703d7..bb3003278516 100644
--- a/crates/ra_proc_macro_srv/Cargo.toml
+++ b/crates/ra_proc_macro_srv/Cargo.toml
@@ -18,7 +18,7 @@ memmap = "0.7"
 test_utils = { path = "../test_utils" }
 
 [dev-dependencies]
-cargo_metadata = "0.9.1"
+cargo_metadata = "0.10.0"
 difference = "2.0.0"
 # used as proc macro test target
-serde_derive = "=1.0.106"
+serde_derive = "1.0.106"
diff --git a/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt b/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt
index 6776f5231743..bc010cfe99d6 100644
--- a/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt
+++ b/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt
@@ -20,26 +20,15 @@ SUBTREE $
   PUNCH   = [alone] 4294967295
   SUBTREE {} 4294967295
     PUNCH   # [alone] 4294967295
-    SUBTREE [] 4294967295
-      IDENT   allow 4294967295
-      SUBTREE () 4294967295
-        IDENT   unknown_lints 4294967295
-    PUNCH   # [alone] 4294967295
-    SUBTREE [] 4294967295
-      IDENT   cfg_attr 4294967295
-      SUBTREE () 4294967295
-        IDENT   feature 4294967295
-        PUNCH   = [alone] 4294967295
-        LITERAL "cargo-clippy" 0
-        PUNCH   , [alone] 4294967295
-        IDENT   allow 4294967295
-        SUBTREE () 4294967295
-          IDENT   useless_attribute 4294967295
-    PUNCH   # [alone] 4294967295
     SUBTREE [] 4294967295
       IDENT   allow 4294967295
       SUBTREE () 4294967295
         IDENT   rust_2018_idioms 4294967295
+        PUNCH   , [alone] 4294967295
+        IDENT   clippy 4294967295
+        PUNCH   : [joint] 4294967295
+        PUNCH   : [alone] 4294967295
+        IDENT   useless_attribute 4294967295
     IDENT   extern 4294967295
     IDENT   crate 4294967295
     IDENT   serde 4294967295
diff --git a/crates/ra_proc_macro_srv/src/tests/mod.rs b/crates/ra_proc_macro_srv/src/tests/mod.rs
index 9cf58511ce7f..82cefbb29a77 100644
--- a/crates/ra_proc_macro_srv/src/tests/mod.rs
+++ b/crates/ra_proc_macro_srv/src/tests/mod.rs
@@ -10,7 +10,7 @@ fn test_derive_serialize_proc_macro() {
     assert_expand(
         "serde_derive",
         "Serialize",
-        "1.0.106",
+        "1.0",
         r##"struct Foo {}"##,
         include_str!("fixtures/test_serialize_proc_macro.txt"),
     );
@@ -21,7 +21,7 @@ fn test_derive_serialize_proc_macro_failed() {
     assert_expand(
         "serde_derive",
         "Serialize",
-        "1.0.106",
+        "1.0",
         r##"
     struct {}
 "##,
@@ -37,7 +37,7 @@ SUBTREE $
 
 #[test]
 fn test_derive_proc_macro_list() {
-    let res = list("serde_derive", "1.0.106").join("\n");
+    let res = list("serde_derive", "1.0").join("\n");
 
     assert_eq_text!(
         &res,
diff --git a/crates/ra_proc_macro_srv/src/tests/utils.rs b/crates/ra_proc_macro_srv/src/tests/utils.rs
index 646a427c5653..84348b5defce 100644
--- a/crates/ra_proc_macro_srv/src/tests/utils.rs
+++ b/crates/ra_proc_macro_srv/src/tests/utils.rs
@@ -8,7 +8,7 @@ use std::str::FromStr;
 use test_utils::assert_eq_text;
 
 mod fixtures {
-    use cargo_metadata::{parse_messages, Message};
+    use cargo_metadata::Message;
     use std::process::Command;
 
     // Use current project metadata to get the proc-macro dylib path
@@ -19,7 +19,7 @@ mod fixtures {
             .unwrap()
             .stdout;
 
-        for message in parse_messages(command.as_slice()) {
+        for message in Message::parse_stream(command.as_slice()) {
             match message.unwrap() {
                 Message::CompilerArtifact(artifact) => {
                     if artifact.target.kind.contains(&"proc-macro".to_string()) {
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index 5e651fe70d9e..e4a60f4c02fd 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -11,11 +11,12 @@ doctest = false
 log = "0.4.8"
 rustc-hash = "1.1.0"
 
-cargo_metadata = "0.9.1"
+cargo_metadata = "0.10.0"
 
 ra_arena = { path = "../ra_arena" }
-ra_db = { path = "../ra_db" }
 ra_cfg = { path = "../ra_cfg" }
+ra_db = { path = "../ra_db" }
+ra_toolchain = { path = "../ra_toolchain" }
 ra_proc_macro =  { path = "../ra_proc_macro" }
 
 serde = { version = "1.0.106", features = ["derive"] }
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 362ee30fe045..a306ce95f36e 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -1,7 +1,6 @@
 //! FIXME: write short doc here
 
 use std::{
-    env,
     ffi::OsStr,
     ops,
     path::{Path, PathBuf},
@@ -56,6 +55,9 @@ pub struct CargoConfig {
 
     /// Runs cargo check on launch to figure out the correct values of OUT_DIR
     pub load_out_dirs_from_check: bool,
+
+    /// rustc target
+    pub target: Option,
 }
 
 impl Default for CargoConfig {
@@ -65,6 +67,7 @@ impl Default for CargoConfig {
             all_features: true,
             features: Vec::new(),
             load_out_dirs_from_check: false,
+            target: None,
         }
     }
 }
@@ -83,6 +86,7 @@ pub struct PackageData {
     pub dependencies: Vec,
     pub edition: Edition,
     pub features: Vec,
+    pub cfgs: Vec,
     pub out_dir: Option,
     pub proc_macro_dylib_path: Option,
 }
@@ -141,12 +145,8 @@ impl CargoWorkspace {
         cargo_toml: &Path,
         cargo_features: &CargoConfig,
     ) -> Result {
-        let _ = Command::new(cargo_binary())
-            .arg("--version")
-            .output()
-            .context("failed to run `cargo --version`, is `cargo` in PATH?")?;
-
         let mut meta = MetadataCommand::new();
+        meta.cargo_path(ra_toolchain::cargo());
         meta.manifest_path(cargo_toml);
         if cargo_features.all_features {
             meta.features(CargoOpt::AllFeatures);
@@ -160,15 +160,20 @@ impl CargoWorkspace {
         if let Some(parent) = cargo_toml.parent() {
             meta.current_dir(parent);
         }
+        if let Some(target) = cargo_features.target.as_ref() {
+            meta.other_options(vec![String::from("--filter-platform"), target.clone()]);
+        }
         let meta = meta.exec().with_context(|| {
             format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
         })?;
 
         let mut out_dir_by_id = FxHashMap::default();
+        let mut cfgs = FxHashMap::default();
         let mut proc_macro_dylib_paths = FxHashMap::default();
         if cargo_features.load_out_dirs_from_check {
             let resources = load_extern_resources(cargo_toml, cargo_features)?;
             out_dir_by_id = resources.out_dirs;
+            cfgs = resources.cfgs;
             proc_macro_dylib_paths = resources.proc_dylib_paths;
         }
 
@@ -194,6 +199,7 @@ impl CargoWorkspace {
                 edition,
                 dependencies: Vec::new(),
                 features: Vec::new(),
+                cfgs: cfgs.get(&id).cloned().unwrap_or_default(),
                 out_dir: out_dir_by_id.get(&id).cloned(),
                 proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
             });
@@ -275,13 +281,14 @@ impl CargoWorkspace {
 pub struct ExternResources {
     out_dirs: FxHashMap,
     proc_dylib_paths: FxHashMap,
+    cfgs: FxHashMap>,
 }
 
 pub fn load_extern_resources(
     cargo_toml: &Path,
     cargo_features: &CargoConfig,
 ) -> Result {
-    let mut cmd = Command::new(cargo_binary());
+    let mut cmd = Command::new(ra_toolchain::cargo());
     cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
     if cargo_features.all_features {
         cmd.arg("--all-features");
@@ -297,13 +304,13 @@ pub fn load_extern_resources(
 
     let mut res = ExternResources::default();
 
-    for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
+    for message in cargo_metadata::Message::parse_stream(output.stdout.as_slice()) {
         if let Ok(message) = message {
             match message {
-                Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
-                    res.out_dirs.insert(package_id, out_dir);
+                Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => {
+                    res.out_dirs.insert(package_id.clone(), out_dir);
+                    res.cfgs.insert(package_id, cfgs);
                 }
-
                 Message::CompilerArtifact(message) => {
                     if message.target.kind.contains(&"proc-macro".to_string()) {
                         let package_id = message.package_id;
@@ -316,6 +323,8 @@ pub fn load_extern_resources(
                 }
                 Message::CompilerMessage(_) => (),
                 Message::Unknown => (),
+                Message::BuildFinished(_) => {}
+                Message::TextLine(_) => {}
             }
         }
     }
@@ -329,7 +338,3 @@ fn is_dylib(path: &Path) -> bool {
         Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
     }
 }
-
-fn cargo_binary() -> String {
-    env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
-}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 731cbd291818..a2e9f65effc0 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -8,7 +8,7 @@ use std::{
     fs::{read_dir, File, ReadDir},
     io::{self, BufReader},
     path::{Path, PathBuf},
-    process::Command,
+    process::{Command, Output},
 };
 
 use anyhow::{bail, Context, Result};
@@ -35,7 +35,7 @@ pub enum ProjectWorkspace {
 /// `PackageRoot` describes a package root folder.
 /// Which may be an external dependency, or a member of
 /// the current workspace.
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct PackageRoot {
     /// Path to the root folder
     path: PathBuf,
@@ -88,46 +88,28 @@ impl ProjectRoot {
     }
 
     pub fn discover(path: &Path) -> io::Result> {
-        if let Some(project_json) = find_rust_project_json(path) {
+        if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
             return Ok(vec![ProjectRoot::ProjectJson(project_json)]);
         }
         return find_cargo_toml(path)
             .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect());
 
-        fn find_rust_project_json(path: &Path) -> Option {
-            if path.ends_with("rust-project.json") {
-                return Some(path.to_path_buf());
-            }
-
-            let mut curr = Some(path);
-            while let Some(path) = curr {
-                let candidate = path.join("rust-project.json");
-                if candidate.exists() {
-                    return Some(candidate);
-                }
-                curr = path.parent();
-            }
-
-            None
-        }
-
         fn find_cargo_toml(path: &Path) -> io::Result> {
-            if path.ends_with("Cargo.toml") {
-                return Ok(vec![path.to_path_buf()]);
+            match find_in_parent_dirs(path, "Cargo.toml") {
+                Some(it) => Ok(vec![it]),
+                None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
             }
-
-            if let Some(p) = find_cargo_toml_in_parent_dir(path) {
-                return Ok(vec![p]);
-            }
-
-            let entities = read_dir(path)?;
-            Ok(find_cargo_toml_in_child_dir(entities))
         }
 
-        fn find_cargo_toml_in_parent_dir(path: &Path) -> Option {
+        fn find_in_parent_dirs(path: &Path, target_file_name: &str) -> Option {
+            if path.ends_with(target_file_name) {
+                return Some(path.to_owned());
+            }
+
             let mut curr = Some(path);
+
             while let Some(path) = curr {
-                let candidate = path.join("Cargo.toml");
+                let candidate = path.join(target_file_name);
                 if candidate.exists() {
                     return Some(candidate);
                 }
@@ -139,14 +121,11 @@ impl ProjectRoot {
 
         fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec {
             // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
-            let mut valid_canditates = vec![];
-            for entity in entities.filter_map(Result::ok) {
-                let candidate = entity.path().join("Cargo.toml");
-                if candidate.exists() {
-                    valid_canditates.push(candidate)
-                }
-            }
-            valid_canditates
+            entities
+                .filter_map(Result::ok)
+                .map(|it| it.path().join("Cargo.toml"))
+                .filter(|it| it.exists())
+                .collect()
         }
     }
 }
@@ -398,7 +377,18 @@ impl ProjectWorkspace {
                             let edition = cargo[pkg].edition;
                             let cfg_options = {
                                 let mut opts = default_cfg_options.clone();
-                                opts.insert_features(cargo[pkg].features.iter().map(Into::into));
+                                for feature in cargo[pkg].features.iter() {
+                                    opts.insert_key_value("feature".into(), feature.into());
+                                }
+                                for cfg in cargo[pkg].cfgs.iter() {
+                                    match cfg.find('=') {
+                                        Some(split) => opts.insert_key_value(
+                                            cfg[..split].into(),
+                                            cfg[split + 1..].trim_matches('"').into(),
+                                        ),
+                                        None => opts.insert_atom(cfg.into()),
+                                    };
+                                }
                                 opts
                             };
                             let mut env = Env::default();
@@ -543,7 +533,7 @@ impl ProjectWorkspace {
     }
 }
 
-pub fn get_rustc_cfg_options() -> CfgOptions {
+pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions {
     let mut cfg_options = CfgOptions::default();
 
     // Some nightly-only cfgs, which are required for stdlib
@@ -556,23 +546,18 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
         }
     }
 
-    match (|| -> Result {
+    let rustc_cfgs = || -> Result {
         // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here.
-        let output = Command::new("rustc")
-            .args(&["--print", "cfg", "-O"])
-            .output()
-            .context("Failed to get output from rustc --print cfg -O")?;
-        if !output.status.success() {
-            bail!(
-                "rustc --print cfg -O exited with exit code ({})",
-                output
-                    .status
-                    .code()
-                    .map_or(String::from("no exit code"), |code| format!("{}", code))
-            );
+        let mut cmd = Command::new(ra_toolchain::rustc());
+        cmd.args(&["--print", "cfg", "-O"]);
+        if let Some(target) = target {
+            cmd.args(&["--target", target.as_str()]);
         }
+        let output = output(cmd)?;
         Ok(String::from_utf8(output.stdout)?)
-    })() {
+    }();
+
+    match rustc_cfgs {
         Ok(rustc_cfgs) => {
             for line in rustc_cfgs.lines() {
                 match line.find('=') {
@@ -585,8 +570,21 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
                 }
             }
         }
-        Err(e) => log::error!("failed to get rustc cfgs: {}", e),
+        Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
     }
 
     cfg_options
 }
+
+fn output(mut cmd: Command) -> Result {
+    let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
+    if !output.status.success() {
+        match String::from_utf8(output.stderr) {
+            Ok(stderr) if !stderr.is_empty() => {
+                bail!("{:?} failed, {}\nstderr:\n{}", cmd, output.status, stderr)
+            }
+            _ => bail!("{:?} failed, {}", cmd, output.status),
+        }
+    }
+    Ok(output)
+}
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs
index 55ff5ad80bd9..a8a196e64c96 100644
--- a/crates/ra_project_model/src/sysroot.rs
+++ b/crates/ra_project_model/src/sysroot.rs
@@ -1,14 +1,16 @@
 //! FIXME: write short doc here
 
-use anyhow::{bail, Context, Result};
 use std::{
     env, ops,
     path::{Path, PathBuf},
-    process::{Command, Output},
+    process::Command,
 };
 
+use anyhow::{bail, Result};
 use ra_arena::{Arena, Idx};
 
+use crate::output;
+
 #[derive(Default, Debug, Clone)]
 pub struct Sysroot {
     crates: Arena,
@@ -84,43 +86,22 @@ impl Sysroot {
     }
 }
 
-fn create_command_text(program: &str, args: &[&str]) -> String {
-    format!("{} {}", program, args.join(" "))
-}
-
-fn run_command_in_cargo_dir(cargo_toml: &Path, program: &str, args: &[&str]) -> Result {
-    let output = Command::new(program)
-        .current_dir(cargo_toml.parent().unwrap())
-        .args(args)
-        .output()
-        .context(format!("{} failed", create_command_text(program, args)))?;
-    if !output.status.success() {
-        match output.status.code() {
-            Some(code) => bail!(
-                "failed to run the command: '{}' exited with code {}",
-                create_command_text(program, args),
-                code
-            ),
-            None => bail!(
-                "failed to run the command: '{}' terminated by signal",
-                create_command_text(program, args)
-            ),
-        };
-    }
-    Ok(output)
-}
-
 fn get_or_install_rust_src(cargo_toml: &Path) -> Result {
     if let Ok(path) = env::var("RUST_SRC_PATH") {
         return Ok(path.into());
     }
-    let rustc_output = run_command_in_cargo_dir(cargo_toml, "rustc", &["--print", "sysroot"])?;
+    let current_dir = cargo_toml.parent().unwrap();
+    let mut rustc = Command::new(ra_toolchain::rustc());
+    rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
+    let rustc_output = output(rustc)?;
     let stdout = String::from_utf8(rustc_output.stdout)?;
     let sysroot_path = Path::new(stdout.trim());
     let src_path = sysroot_path.join("lib/rustlib/src/rust/src");
 
     if !src_path.exists() {
-        run_command_in_cargo_dir(cargo_toml, "rustup", &["component", "add", "rust-src"])?;
+        let mut rustup = Command::new(ra_toolchain::rustup());
+        rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
+        let _output = output(rustup)?;
     }
     if !src_path.exists() {
         bail!(
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index 2a8dac757b61..664894d1f839 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -266,6 +266,15 @@ impl<'a> SyntaxRewriter<'a> {
         let replacement = Replacement::Single(with.clone().into());
         self.replacements.insert(what, replacement);
     }
+    pub fn replace_with_many>(
+        &mut self,
+        what: &T,
+        with: Vec,
+    ) {
+        let what = what.clone().into();
+        let replacement = Replacement::Many(with);
+        self.replacements.insert(what, replacement);
+    }
     pub fn replace_ast(&mut self, what: &T, with: &T) {
         self.replace(what.syntax(), with.syntax())
     }
@@ -302,31 +311,41 @@ impl<'a> SyntaxRewriter<'a> {
 
     fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode {
         //  FIXME: this could be made much faster.
-        let new_children =
-            node.children_with_tokens().flat_map(|it| self.rewrite_self(&it)).collect::>();
+        let mut new_children = Vec::new();
+        for child in node.children_with_tokens() {
+            self.rewrite_self(&mut new_children, &child);
+        }
         with_children(node, new_children)
     }
 
     fn rewrite_self(
         &self,
+        acc: &mut Vec>,
         element: &SyntaxElement,
-    ) -> Option> {
+    ) {
         if let Some(replacement) = self.replacement(&element) {
-            return match replacement {
+            match replacement {
                 Replacement::Single(NodeOrToken::Node(it)) => {
-                    Some(NodeOrToken::Node(it.green().clone()))
+                    acc.push(NodeOrToken::Node(it.green().clone()))
                 }
                 Replacement::Single(NodeOrToken::Token(it)) => {
-                    Some(NodeOrToken::Token(it.green().clone()))
+                    acc.push(NodeOrToken::Token(it.green().clone()))
                 }
-                Replacement::Delete => None,
+                Replacement::Many(replacements) => {
+                    acc.extend(replacements.iter().map(|it| match it {
+                        NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
+                        NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
+                    }))
+                }
+                Replacement::Delete => (),
             };
+            return;
         }
         let res = match element {
             NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
             NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()),
         };
-        Some(res)
+        acc.push(res)
     }
 }
 
@@ -341,6 +360,7 @@ impl ops::AddAssign for SyntaxRewriter<'_> {
 enum Replacement {
     Delete,
     Single(SyntaxElement),
+    Many(Vec),
 }
 
 fn with_children(
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 521ca8ab8eda..1876afe958e0 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -16,9 +16,7 @@ use crate::{
 };
 
 pub use self::{
-    expr_extensions::{
-        ArrayExprKind, BinOp, BlockModifier, ElseBranch, LiteralKind, PrefixOp, RangeOp,
-    },
+    expr_extensions::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, RangeOp},
     extensions::{
         AttrKind, FieldKind, NameOrNameRef, PathSegmentKind, SelfParamKind, SlicePatComponents,
         StructKind, TypeBoundKind, VisibilityKind,
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 26e4576ffe99..29eb3fcb9ccc 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -1,7 +1,10 @@
 //! This module contains functions for editing syntax trees. As the trees are
 //! immutable, all function here return a fresh copy of the tree, instead of
 //! doing an in-place modification.
-use std::{iter, ops::RangeInclusive};
+use std::{
+    fmt, iter,
+    ops::{self, RangeInclusive},
+};
 
 use arrayvec::ArrayVec;
 
@@ -28,7 +31,7 @@ impl ast::BinExpr {
 
 impl ast::FnDef {
     #[must_use]
-    pub fn with_body(&self, body: ast::Block) -> ast::FnDef {
+    pub fn with_body(&self, body: ast::BlockExpr) -> ast::FnDef {
         let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
         let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() {
             old_body.syntax().clone().into()
@@ -79,7 +82,7 @@ where
 
 impl ast::ItemList {
     #[must_use]
-    pub fn append_items(&self, items: impl IntoIterator) -> ast::ItemList {
+    pub fn append_items(&self, items: impl IntoIterator) -> ast::ItemList {
         let mut res = self.clone();
         if !self.syntax().text().contains_char('\n') {
             res = make_multiline(res);
@@ -89,8 +92,8 @@ impl ast::ItemList {
     }
 
     #[must_use]
-    pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
-        let (indent, position) = match self.impl_items().last() {
+    pub fn append_item(&self, item: ast::AssocItem) -> ast::ItemList {
+        let (indent, position) = match self.assoc_items().last() {
             Some(it) => (
                 leading_indent(it.syntax()).unwrap_or_default().to_string(),
                 InsertPosition::After(it.syntax().clone().into()),
@@ -437,6 +440,28 @@ impl From for IndentLevel {
     }
 }
 
+impl fmt::Display for IndentLevel {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let spaces = "                                        ";
+        let buf;
+        let len = self.0 as usize * 4;
+        let indent = if len <= spaces.len() {
+            &spaces[..len]
+        } else {
+            buf = iter::repeat(' ').take(len).collect::();
+            &buf
+        };
+        fmt::Display::fmt(indent, f)
+    }
+}
+
+impl ops::Add for IndentLevel {
+    type Output = IndentLevel;
+    fn add(self, rhs: u8) -> IndentLevel {
+        IndentLevel(self.0 + rhs)
+    }
+}
+
 impl IndentLevel {
     pub fn from_node(node: &SyntaxNode) -> IndentLevel {
         let first_token = match node.first_token() {
@@ -453,11 +478,15 @@ impl IndentLevel {
         IndentLevel(0)
     }
 
-    pub fn increase_indent(self, node: N) -> N {
-        N::cast(self._increase_indent(node.syntax().clone())).unwrap()
-    }
-
-    fn _increase_indent(self, node: SyntaxNode) -> SyntaxNode {
+    /// XXX: this intentionally doesn't change the indent of the very first token.
+    /// Ie, in something like
+    /// ```
+    /// fn foo() {
+    ///    92
+    /// }
+    /// ```
+    /// if you indent the block, the `{` token would stay put.
+    fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
         let mut rewriter = SyntaxRewriter::default();
         node.descendants_with_tokens()
             .filter_map(|el| el.into_token())
@@ -467,22 +496,13 @@ impl IndentLevel {
                 text.contains('\n')
             })
             .for_each(|ws| {
-                let new_ws = make::tokens::whitespace(&format!(
-                    "{}{:width$}",
-                    ws.syntax().text(),
-                    "",
-                    width = self.0 as usize * 4
-                ));
+                let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,));
                 rewriter.replace(ws.syntax(), &new_ws)
             });
         rewriter.rewrite(&node)
     }
 
-    pub fn decrease_indent(self, node: N) -> N {
-        N::cast(self._decrease_indent(node.syntax().clone())).unwrap()
-    }
-
-    fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
+    fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
         let mut rewriter = SyntaxRewriter::default();
         node.descendants_with_tokens()
             .filter_map(|el| el.into_token())
@@ -493,7 +513,7 @@ impl IndentLevel {
             })
             .for_each(|ws| {
                 let new_ws = make::tokens::whitespace(
-                    &ws.syntax().text().replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"),
+                    &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
                 );
                 rewriter.replace(ws.syntax(), &new_ws)
             });
@@ -521,7 +541,7 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator {
     iter::successors(Some(token), |token| token.prev_token())
 }
 
-pub trait AstNodeEdit: AstNode + Sized {
+pub trait AstNodeEdit: AstNode + Clone + Sized {
     #[must_use]
     fn insert_children(
         &self,
@@ -558,9 +578,17 @@ pub trait AstNodeEdit: AstNode + Sized {
         }
         rewriter.rewrite_ast(self)
     }
+    #[must_use]
+    fn indent(&self, indent: IndentLevel) -> Self {
+        Self::cast(indent.increase_indent(self.syntax().clone())).unwrap()
+    }
+    #[must_use]
+    fn dedent(&self, indent: IndentLevel) -> Self {
+        Self::cast(indent.decrease_indent(self.syntax().clone())).unwrap()
+    }
 }
 
-impl AstNodeEdit for N {}
+impl AstNodeEdit for N {}
 
 fn single_node(element: impl Into) -> RangeInclusive {
     let element = element.into();
@@ -580,7 +608,7 @@ fn test_increase_indent() {
     _ => (),
 }"
     );
-    let indented = IndentLevel(2).increase_indent(arm_list);
+    let indented = arm_list.indent(IndentLevel(2));
     assert_eq!(
         indented.syntax().to_string(),
         "{
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs
index 352c0d2c57e2..7771d6759553 100644
--- a/crates/ra_syntax/src/ast/expr_extensions.rs
+++ b/crates/ra_syntax/src/ast/expr_extensions.rs
@@ -16,7 +16,7 @@ impl ast::Expr {
             | ast::Expr::WhileExpr(_)
             | ast::Expr::BlockExpr(_)
             | ast::Expr::MatchExpr(_)
-            | ast::Expr::TryExpr(_) => true,
+            | ast::Expr::EffectExpr(_) => true,
             _ => false,
         }
     }
@@ -43,7 +43,7 @@ impl ast::IfExpr {
         Some(res)
     }
 
-    fn blocks(&self) -> AstChildren {
+    pub fn blocks(&self) -> AstChildren {
         support::children(self.syntax())
     }
 }
@@ -359,22 +359,34 @@ impl ast::Literal {
     }
 }
 
-pub enum BlockModifier {
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Effect {
     Async(SyntaxToken),
     Unsafe(SyntaxToken),
+    Try(SyntaxToken),
+    // Very much not an effect, but we stuff it into this node anyway
+    Label(ast::Label),
+}
+
+impl ast::EffectExpr {
+    pub fn effect(&self) -> Effect {
+        if let Some(token) = self.async_token() {
+            return Effect::Async(token);
+        }
+        if let Some(token) = self.unsafe_token() {
+            return Effect::Unsafe(token);
+        }
+        if let Some(token) = self.try_token() {
+            return Effect::Try(token);
+        }
+        if let Some(label) = self.label() {
+            return Effect::Label(label);
+        }
+        unreachable!("ast::EffectExpr without Effect")
+    }
 }
 
 impl ast::BlockExpr {
-    pub fn modifier(&self) -> Option {
-        if let Some(token) = self.async_token() {
-            return Some(BlockModifier::Async(token));
-        }
-        if let Some(token) = self.unsafe_token() {
-            return Some(BlockModifier::Unsafe(token));
-        }
-        None
-    }
-
     /// false if the block is an intrinsic part of the syntax and can't be
     /// replaced with arbitrary expression.
     ///
@@ -383,15 +395,12 @@ impl ast::BlockExpr {
     /// const FOO: () = { stand_alone };
     /// ```
     pub fn is_standalone(&self) -> bool {
-        if self.modifier().is_some() {
-            return false;
-        }
         let parent = match self.syntax().parent() {
             Some(it) => it,
             None => return true,
         };
         match parent.kind() {
-            FN_DEF | IF_EXPR | WHILE_EXPR | LOOP_EXPR => false,
+            FN_DEF | IF_EXPR | WHILE_EXPR | LOOP_EXPR | EFFECT_EXPR => false,
             _ => true,
         }
     }
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 45e3dd2d3e4c..98c38d0095a9 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -423,6 +423,10 @@ impl ast::MacroCall {
             None
         }
     }
+
+    pub fn is_bang(&self) -> bool {
+        self.is_macro_rules().is_none()
+    }
 }
 
 impl ast::LifetimeParam {
@@ -463,7 +467,7 @@ impl ast::TokenTree {
 
     pub fn right_delimiter_token(&self) -> Option {
         self.syntax().last_child_or_token()?.into_token().filter(|it| match it.kind() {
-            T!['{'] | T!['('] | T!['['] => true,
+            T!['}'] | T![')'] | T![']'] => true,
             _ => false,
         })
     }
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs
index 3f16592b69c9..cf6067e57c42 100644
--- a/crates/ra_syntax/src/ast/generated/nodes.rs
+++ b/crates/ra_syntax/src/ast/generated/nodes.rs
@@ -5,17 +5,41 @@ use crate::{
     SyntaxKind::{self, *},
     SyntaxNode, SyntaxToken, T,
 };
-
+/// The entire Rust source file. Includes all top-level inner attributes and module items.
+///
+/// [Reference](https://doc.rust-lang.org/reference/crates-and-source-files.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct SourceFile {
     pub(crate) syntax: SyntaxNode,
 }
 impl ast::ModuleItemOwner for SourceFile {}
 impl ast::AttrsOwner for SourceFile {}
+impl ast::DocCommentsOwner for SourceFile {}
 impl SourceFile {
     pub fn modules(&self) -> AstChildren { support::children(&self.syntax) }
 }
-
+/// Function definition either with body or not.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub extern "C" fn foo(#[attr] Patern {p}: Pattern) -> u32
+///     where
+///         T: Debug
+///     {
+///         42
+///     }
+/// ❱
+///
+/// extern "C" {
+///     ❰ fn fn_decl(also_variadic_ffi: u32, ...) -> u32; ❱
+/// }
+/// ```
+///
+/// - [Reference](https://doc.rust-lang.org/reference/items/functions.html)
+/// - [Nomicon](https://doc.rust-lang.org/nomicon/ffi.html#variadic-functions)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct FnDef {
     pub(crate) syntax: SyntaxNode,
@@ -37,7 +61,13 @@ impl FnDef {
     pub fn body(&self) -> Option { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
 }
-
+/// Return type annotation.
+///
+/// ```
+/// fn foo(a: u32) ❰ -> Option ❱ { Some(a) }
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/functions.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct RetType {
     pub(crate) syntax: SyntaxNode,
@@ -46,7 +76,26 @@ impl RetType {
     pub fn thin_arrow_token(&self) -> Option { support::token(&self.syntax, T![->]) }
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Struct definition.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     struct Foo where T: Debug {
+///         /// Docs
+///         #[attr]
+///         pub a: u32,
+///         b: T,
+///     }
+/// ❱
+///
+/// ❰ struct Foo; ❱
+/// ❰ struct Foo(#[attr] T) where T: Debug; ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/structs.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct StructDef {
     pub(crate) syntax: SyntaxNode,
@@ -61,7 +110,23 @@ impl StructDef {
     pub fn field_def_list(&self) -> Option { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
 }
-
+/// Union definition.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub union Foo where T: Debug {
+///         /// Docs
+///         #[attr]
+///         a: T,
+///         b: u32,
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/unions.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct UnionDef {
     pub(crate) syntax: SyntaxNode,
@@ -77,7 +142,19 @@ impl UnionDef {
         support::child(&self.syntax)
     }
 }
-
+/// Record field definition list including enclosing curly braces.
+///
+/// ```
+/// struct Foo // same for union
+/// ❰
+///     {
+///         a: u32,
+///         b: bool,
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/structs.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct RecordFieldDefList {
     pub(crate) syntax: SyntaxNode,
@@ -87,7 +164,22 @@ impl RecordFieldDefList {
     pub fn fields(&self) -> AstChildren { support::children(&self.syntax) }
     pub fn r_curly_token(&self) -> Option { support::token(&self.syntax, T!['}']) }
 }
-
+/// Record field definition including its attributes and doc comments.
+///
+/// ` ``
+/// same for union
+/// struct Foo {
+///      ❰
+///          /// Docs
+///          #[attr]
+///          pub a: u32
+///      ❱
+///
+///      ❰ b: bool ❱
+/// }
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/structs.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct RecordFieldDef {
     pub(crate) syntax: SyntaxNode,
@@ -98,7 +190,13 @@ impl ast::AttrsOwner for RecordFieldDef {}
 impl ast::DocCommentsOwner for RecordFieldDef {}
 impl ast::TypeAscriptionOwner for RecordFieldDef {}
 impl RecordFieldDef {}
-
+/// Tuple field definition list including enclosing parens.
+///
+/// ```
+/// struct Foo ❰ (u32, String, Vec) ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/structs.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct TupleFieldDefList {
     pub(crate) syntax: SyntaxNode,
@@ -108,7 +206,13 @@ impl TupleFieldDefList {
     pub fn fields(&self) -> AstChildren { support::children(&self.syntax) }
     pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) }
 }
-
+/// Tuple field definition including its attributes.
+///
+/// ```
+/// struct Foo(❰ #[attr] u32 ❱);
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/structs.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct TupleFieldDef {
     pub(crate) syntax: SyntaxNode,
@@ -118,7 +222,29 @@ impl ast::AttrsOwner for TupleFieldDef {}
 impl TupleFieldDef {
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Enum definition.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub enum Foo where T: Debug {
+///         /// Docs
+///         #[attr]
+///         Bar,
+///         Baz(#[attr] u32),
+///         Bruh {
+///             a: u32,
+///             /// Docs
+///             #[attr]
+///             b: T,
+///         }
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/enumerations.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct EnumDef {
     pub(crate) syntax: SyntaxNode,
@@ -132,7 +258,22 @@ impl EnumDef {
     pub fn enum_token(&self) -> Option { support::token(&self.syntax, T![enum]) }
     pub fn variant_list(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Enum variant definition list including enclosing curly braces.
+///
+/// ```
+/// enum Foo
+/// ❰
+///     {
+///         Bar,
+///         Baz(u32),
+///         Bruh {
+///             a: u32
+///         }
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/enumerations.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct EnumVariantList {
     pub(crate) syntax: SyntaxNode,
@@ -142,7 +283,21 @@ impl EnumVariantList {
     pub fn variants(&self) -> AstChildren { support::children(&self.syntax) }
     pub fn r_curly_token(&self) -> Option { support::token(&self.syntax, T!['}']) }
 }
-
+/// Enum variant definition including its attributes and discriminant value definition.
+///
+/// ```
+/// enum Foo {
+///     ❰
+///         /// Docs
+///         #[attr]
+///         Bar
+///     ❱
+///
+///     // same for tuple and record variants
+/// }
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/enumerations.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct EnumVariant {
     pub(crate) syntax: SyntaxNode,
@@ -156,7 +311,20 @@ impl EnumVariant {
     pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) }
     pub fn expr(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Trait definition.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub unsafe trait Foo: Debug where T: Debug {
+///         // ...
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/traits.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct TraitDef {
     pub(crate) syntax: SyntaxNode,
@@ -173,7 +341,27 @@ impl TraitDef {
     pub fn trait_token(&self) -> Option { support::token(&self.syntax, T![trait]) }
     pub fn item_list(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Module definition either with body or not.
+/// Includes all of its inner and outer attributes, module items, doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub mod foo;
+/// ❱
+///
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub mod bar {
+///        //! Inner docs
+///        #![inner_attr]
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/modules.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Module {
     pub(crate) syntax: SyntaxNode,
@@ -187,7 +375,28 @@ impl Module {
     pub fn item_list(&self) -> Option { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
 }
-
+/// Item defintion list.
+/// This is used for both top-level items and impl block items.
+///
+/// ```
+/// ❰
+///     fn foo {}
+///     struct Bar;
+///     enum Baz;
+///     trait Bruh;
+///     const BRUUH: u32 = 42;
+/// ❱
+///
+/// impl Foo
+/// ❰
+///     {
+///         fn bar() {}
+///         const BAZ: u32 = 42;
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ItemList {
     pub(crate) syntax: SyntaxNode,
@@ -195,10 +404,21 @@ pub struct ItemList {
 impl ast::ModuleItemOwner for ItemList {}
 impl ItemList {
     pub fn l_curly_token(&self) -> Option { support::token(&self.syntax, T!['{']) }
-    pub fn impl_items(&self) -> AstChildren { support::children(&self.syntax) }
+    pub fn assoc_items(&self) -> AstChildren { support::children(&self.syntax) }
     pub fn r_curly_token(&self) -> Option { support::token(&self.syntax, T!['}']) }
 }
-
+/// Constant variable definition.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub const FOO: u32 = 42;
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/constant-items.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ConstDef {
     pub(crate) syntax: SyntaxNode,
@@ -216,7 +436,18 @@ impl ConstDef {
     pub fn body(&self) -> Option { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
 }
-
+/// Static variable definition.
+/// Includes all of its attributes and doc comments.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub static mut FOO: u32 = 42;
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/static-items.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct StaticDef {
     pub(crate) syntax: SyntaxNode,
@@ -234,7 +465,24 @@ impl StaticDef {
     pub fn body(&self) -> Option { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
 }
-
+/// Type alias definition.
+/// Includes associated type clauses with type bounds.
+///
+/// ```
+/// ❰
+///     /// Docs
+///     #[attr]
+///     pub type Foo where T: Debug = T;
+/// ❱
+///
+/// trait Bar {
+///     ❰ type Baz: Debug; ❱
+///     ❰ type Bruh = String; ❱
+///     ❰ type Bruuh: Debug = u32; ❱
+/// }
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/type-aliases.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct TypeAliasDef {
     pub(crate) syntax: SyntaxNode,
@@ -252,13 +500,27 @@ impl TypeAliasDef {
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
 }
-
+/// Inherent and trait impl definition.
+/// Includes all of its inner and outer attributes.
+///
+/// ```
+/// ❰
+///     #[attr]
+///     unsafe impl const !Foo for Bar where T: Debug {
+///         #![inner_attr]
+///         // ...
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/items/implementations.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ImplDef {
     pub(crate) syntax: SyntaxNode,
 }
 impl ast::TypeParamsOwner for ImplDef {}
 impl ast::AttrsOwner for ImplDef {}
+impl ast::DocCommentsOwner for ImplDef {}
 impl ImplDef {
     pub fn default_token(&self) -> Option { support::token(&self.syntax, T![default]) }
     pub fn const_token(&self) -> Option { support::token(&self.syntax, T![const]) }
@@ -268,7 +530,16 @@ impl ImplDef {
     pub fn for_token(&self) -> Option { support::token(&self.syntax, T![for]) }
     pub fn item_list(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Parenthesized type reference.
+/// Note: parens are only used for grouping, this is not a tuple type.
+///
+/// ```
+/// // This is effectively just `u32`.
+/// // Single-item tuple must be defined with a trailing comma: `(u32,)`
+/// type Foo = ❰ (u32) ❱;
+///
+/// let bar: &'static ❰ (dyn Debug) ❱ = "bruh";
+/// ```
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ParenType {
     pub(crate) syntax: SyntaxNode,
@@ -278,7 +549,13 @@ impl ParenType {
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
     pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) }
 }
-
+/// Unnamed tuple type.
+///
+/// ```
+/// let foo: ❰ (u32, bool) ❱ = (42, true);
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/tuple.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct TupleType {
     pub(crate) syntax: SyntaxNode,
@@ -288,7 +565,17 @@ impl TupleType {
     pub fn fields(&self) -> AstChildren { support::children(&self.syntax) }
     pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) }
 }
-
+/// The never type (i.e. the exclamation point).
+///
+/// ```
+/// type T = ❰ ! ❱;
+///
+/// fn no_return() -> ❰ ! ❱ {
+///     loop {}
+/// }
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/never.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct NeverType {
     pub(crate) syntax: SyntaxNode,
@@ -296,7 +583,17 @@ pub struct NeverType {
 impl NeverType {
     pub fn excl_token(&self) -> Option { support::token(&self.syntax, T![!]) }
 }
-
+/// Path to a type.
+/// Includes single identifier type names and elaborate paths with
+/// generic parameters.
+///
+/// ```
+/// type Foo = ❰ String ❱;
+/// type Bar = ❰ std::vec::Vec ❱;
+/// type Baz = ❰ ::bruh::::Item ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/paths.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct PathType {
     pub(crate) syntax: SyntaxNode,
@@ -304,7 +601,14 @@ pub struct PathType {
 impl PathType {
     pub fn path(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Raw pointer type.
+///
+/// ```
+/// type Foo = ❰ *const u32 ❱;
+/// type Bar = ❰ *mut u32 ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/pointer.html#raw-pointers-const-and-mut)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct PointerType {
     pub(crate) syntax: SyntaxNode,
@@ -315,7 +619,13 @@ impl PointerType {
     pub fn mut_token(&self) -> Option { support::token(&self.syntax, T![mut]) }
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Array type.
+///
+/// ```
+/// type Foo = ❰ [u32; 24 - 3] ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/array.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ArrayType {
     pub(crate) syntax: SyntaxNode,
@@ -327,7 +637,13 @@ impl ArrayType {
     pub fn expr(&self) -> Option { support::child(&self.syntax) }
     pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) }
 }
-
+/// Slice type.
+///
+/// ```
+/// type Foo = ❰ [u8] ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/slice.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct SliceType {
     pub(crate) syntax: SyntaxNode,
@@ -337,7 +653,13 @@ impl SliceType {
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
     pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) }
 }
-
+/// Reference type.
+///
+/// ```
+/// type Foo = ❰ &'static str ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/pointer.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ReferenceType {
     pub(crate) syntax: SyntaxNode,
@@ -350,7 +672,13 @@ impl ReferenceType {
     pub fn mut_token(&self) -> Option { support::token(&self.syntax, T![mut]) }
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Placeholder type (i.e. the underscore).
+///
+/// ```
+/// let foo: ❰ _ ❱ = 42_u32;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/inferred.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct PlaceholderType {
     pub(crate) syntax: SyntaxNode,
@@ -358,7 +686,15 @@ pub struct PlaceholderType {
 impl PlaceholderType {
     pub fn underscore_token(&self) -> Option { support::token(&self.syntax, T![_]) }
 }
-
+/// Function pointer type (not to be confused with `Fn*` family of traits).
+///
+/// ```
+/// type Foo = ❰ async fn(#[attr] u32, named: bool) -> u32 ❱;
+///
+/// type Bar = ❰ extern "C" fn(variadic: u32, #[attr] ...) ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/function-pointer.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct FnPointerType {
     pub(crate) syntax: SyntaxNode,
@@ -370,7 +706,13 @@ impl FnPointerType {
     pub fn param_list(&self) -> Option { support::child(&self.syntax) }
     pub fn ret_type(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Higher order type.
+///
+/// ```
+/// type Foo = ❰ for<'a> fn(&'a str) ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/nomicon/hrtb.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ForType {
     pub(crate) syntax: SyntaxNode,
@@ -380,7 +722,13 @@ impl ForType {
     pub fn type_param_list(&self) -> Option { support::child(&self.syntax) }
     pub fn type_ref(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Opaque `impl Trait` type.
+///
+/// ```
+/// fn foo(bar: ❰ impl Debug + Eq ❱) {}
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/impl-trait.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ImplTraitType {
     pub(crate) syntax: SyntaxNode,
@@ -389,7 +737,13 @@ impl ast::TypeBoundsOwner for ImplTraitType {}
 impl ImplTraitType {
     pub fn impl_token(&self) -> Option { support::token(&self.syntax, T![impl]) }
 }
-
+/// Trait object type.
+///
+/// ```
+/// type Foo = ❰ dyn Debug ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/types/trait-object.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct DynTraitType {
     pub(crate) syntax: SyntaxNode,
@@ -398,7 +752,13 @@ impl ast::TypeBoundsOwner for DynTraitType {}
 impl DynTraitType {
     pub fn dyn_token(&self) -> Option { support::token(&self.syntax, T![dyn]) }
 }
-
+/// Tuple literal.
+///
+/// ```
+/// ❰ (42, true) ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/tuple-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct TupleExpr {
     pub(crate) syntax: SyntaxNode,
@@ -409,7 +769,15 @@ impl TupleExpr {
     pub fn exprs(&self) -> AstChildren { support::children(&self.syntax) }
     pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) }
 }
-
+/// Array literal.
+///
+/// ```
+/// ❰ [#![inner_attr] true, false, true] ❱;
+///
+/// ❰ ["baz"; 24] ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/array-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ArrayExpr {
     pub(crate) syntax: SyntaxNode,
@@ -421,7 +789,14 @@ impl ArrayExpr {
     pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) }
     pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) }
 }
-
+/// Parenthesized expression.
+/// Note: parens are only used for grouping, this is not a tuple literal.
+///
+/// ```
+/// ❰ (#![inner_attr] 2 + 2) ❱ * 2;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/grouped-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ParenExpr {
     pub(crate) syntax: SyntaxNode,
@@ -432,7 +807,19 @@ impl ParenExpr {
     pub fn expr(&self) -> Option { support::child(&self.syntax) }
     pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) }
 }
-
+/// Path to a symbol in expression context.
+/// Includes single identifier variable names and elaborate paths with
+/// generic parameters.
+///
+/// ```
+/// ❰ Some:: ❱;
+/// ❰ foo ❱ + 42;
+/// ❰ Vec::::push ❱;
+/// ❰ <[i32]>::reverse ❱;
+/// ❰ >::borrow ❱;
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/path-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct PathExpr {
     pub(crate) syntax: SyntaxNode,
@@ -440,7 +827,17 @@ pub struct PathExpr {
 impl PathExpr {
     pub fn path(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Anonymous callable object literal a.k.a. closure, lambda or functor.
+///
+/// ```
+/// ❰ || 42 ❱;
+/// ❰ |a: u32| val + 1 ❱;
+/// ❰ async |#[attr] Pattern(_): Pattern| { bar } ❱;
+/// ❰ move || baz ❱;
+/// ❰ || -> u32 { closure_with_ret_type_annotation_requires_block_expr } ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/closure-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct LambdaExpr {
     pub(crate) syntax: SyntaxNode,
@@ -454,7 +851,25 @@ impl LambdaExpr {
     pub fn ret_type(&self) -> Option { support::child(&self.syntax) }
     pub fn body(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// If expression. Includes both regular `if` and `if let` forms.
+/// Beware that `else if` is a special case syntax sugar, because in general
+/// there has to be block expression after `else`.
+///
+/// ```
+/// ❰ if bool_cond { 42 } ❱
+/// ❰ if bool_cond { 42 } else { 24 } ❱
+/// ❰ if bool_cond { 42 } else if bool_cond2 { 42 } ❱
+///
+/// ❰
+///     if let Pattern(foo) = bar {
+///         foo
+///     } else {
+///         panic!();
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/if-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct IfExpr {
     pub(crate) syntax: SyntaxNode,
@@ -464,7 +879,17 @@ impl IfExpr {
     pub fn if_token(&self) -> Option { support::token(&self.syntax, T![if]) }
     pub fn condition(&self) -> Option { support::child(&self.syntax) }
 }
-
+/// Unconditional loop expression.
+///
+/// ```
+/// ❰
+///     loop {
+///         // yeah, it's that simple...
+///     }
+/// ❱
+/// ```
+///
+/// [Reference](https://doc.rust-lang.org/reference/expressions/loop-expr.html)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct LoopExpr {
     pub(crate) syntax: SyntaxNode,
@@ -474,7 +899,45 @@ impl ast::LoopBodyOwner for LoopExpr {}
 impl LoopExpr {
     pub fn loop_token(&self) -> Option { support::token(&self.syntax, T![loop]) }
 }
-
+/// Block expression with an optional prefix (label, try ketword,
+/// unsafe keyword, async keyword...).
+///
+/// ```
+/// ❰
+///     'label: try {
+///         None?
+///     }
+/// ❱
+/// ```
+///
+/// - [try block](https://doc.rust-lang.org/unstable-book/language-features/try-blocks.html)
+/// - [unsafe block](https://doc.rust-lang.org/reference/expressions/block-expr.html#unsafe-blocks)
+/// - [async block](https://doc.rust-lang.org/reference/expressions/block-expr.html#async-blocks)
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct EffectExpr {
+    pub(crate) syntax: SyntaxNode,
+}
+impl ast::AttrsOwner for EffectExpr {}
+impl EffectExpr {
+    pub fn label(&self) -> Option