Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into modname_spacing

This commit is contained in:
Galilée 'Bill' Enguehard 2020-05-21 23:27:38 +02:00
commit 7fece3bdd2
443 changed files with 26839 additions and 17941 deletions

View File

@ -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:

201
Cargo.lock generated
View File

@ -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",
]

View File

@ -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<SnippetCap>,
}
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: () }) }
}
}

View File

@ -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<SyntaxToken> {
self.source_file.syntax().token_at_offset(self.offset())
}
pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
self.token_at_offset().find(|it| it.kind() == kind)
}
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
find_node_at_offset(self.source_file.syntax(), self.offset())
}
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
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<SourceChange>)>,
}
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<Assist> {
assert!(!self.resolve);
self.finish()
.into_iter()
.map(|(label, edit)| {
assert!(edit.is_none());
label
})
.collect()
}
pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
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<String>,
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<String>,
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<SourceChange>)> {
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<String>) {
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<String>,
) {
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<String>) {
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<String>,
) {
self.is_snippet = true;
self.replace(range, snippet);
}
pub(crate) fn replace_ast<N: AstNode>(&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<String>,
) {
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
}
}

View File

@ -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<AssistInfo>);
#[derive(Clone, Debug)]
pub(crate) struct AssistInfo {
pub(crate) label: AssistLabel,
pub(crate) group_label: Option<GroupLabel>,
pub(crate) action: Option<AssistAction>,
}
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<ResolvedAssist> {
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<Assist>;
/// `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<String>,
f: impl FnOnce(&mut ActionBuilder),
) -> Option<Assist> {
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<String>) -> AssistGroup<'a> {
AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() }
}
pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
self.source_file.syntax().token_at_offset(self.frange.range.start())
}
pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
self.token_at_offset().find(|it| it.kind() == kind)
}
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
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<AssistInfo>,
}
impl<'a> AssistGroup<'a> {
pub(crate) fn add_assist(
&mut self,
id: AssistId,
label: impl Into<String>,
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<Assist> {
if self.assists.is_empty() {
None
} else {
Some(Assist(self.assists))
}
}
}
#[derive(Default)]
pub(crate) struct ActionBuilder {
edit: TextEditBuilder,
cursor_position: Option<TextSize>,
target: Option<TextRange>,
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<String>) {
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<String>,
) {
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<String>) {
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<N: AstNode>(&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,
}
}
}

View File

@ -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,

View File

@ -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::<Vec<_>>()
.join(", ")
)
});
let actual = assist.action.edit.apply(&before);
assert_eq_text!(after, &actual);
}

View File

@ -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<Assist> {
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
@ -46,11 +49,10 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
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<Assist> {
.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
}
",
)

View File

@ -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<Assist> {
pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let cap = ctx.config.snippet_cap?;
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
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<TextSize> {
#[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, }
",
);

View File

@ -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<Assist> {
pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let stmt = ctx.find_node_at_offset::<LetStmt>()?;
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> {
// 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<Assist> {
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<Assist> {
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<i32> = Option::Some(1);
let a: Option<i32> = 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<K, T = u8> {
}
fn main() {
let test<|>: Test<i32> = Test { t: 23, k: 33 };
let test: Test<i32> = Test { t: 23, k: 33 };
}"#,
);
}

View File

@ -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<Assist> {
pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
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<Assist> {
}
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<u32> for A {
impl From<u32> 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<foo::bar::baz::Boo> for A {
impl From<foo::bar::baz::Boo> 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<T> {
}"#,
r#"enum A { One(u32), Two(String), }
<|>impl From<u32> for A {
impl From<u32> for A {
fn from(v: u32) -> Self {
A::One(v)
}

View File

@ -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<Assist> {
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<Assist> {
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<SnippetCap>) -> 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<ast::TypeParamList>,
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<hir::InFile<hir::ModuleSource>>,
target_module: Option<hir::Module>,
) -> Option<Self> {
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<FunctionTemplate> {
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<ast::Name> {
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::TypeParamList>, 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<String> {
}
}
fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
fn fn_arg_type(
ctx: &AssistContext,
target_module: hir::Module,
fn_arg: &ast::Expr,
) -> Option<String> {
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<GeneratedFu
fn next_space_for_fn_in_module(
db: &dyn hir::db::AstDatabase,
module: hir::InFile<hir::ModuleSource>,
) -> 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<hir::ModuleSource>,
) -> 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: T) {
}
fn bar<T>(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() {

View File

@ -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<T: Clone> {
// data: T,<|>
// data: T,<|>
// }
// ```
// ->
// ```
// struct Ctx<T: Clone> {
// data: T,
// data: T,
// }
//
// impl<T: Clone> Ctx<T> {
//
// $0
// }
// ```
pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
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<Assist> {
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<T: Clone> {<|>}",
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\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}",
);
}

View File

@ -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<u32> for () {
// fn foo(&self) -> u32 { todo!() }
// fn foo(&self) -> u32 {
// ${0:todo!()}
// }
//
// }
// ```
pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
// impl Trait for () {
// Type X = ();
// fn foo(&self) {}
// fn bar(&self) {}
// $0fn bar(&self) {}
//
// }
// ```
pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
}
fn add_missing_impl_members_inner(
ctx: AssistCtx,
acc: &mut Assists,
ctx: &AssistContext,
mode: AddMissingImplMembersMode,
assist_id: &'static str,
label: &'static str,
) -> Option<Assist> {
) -> Option<()> {
let _p = ra_prof::profile("add_missing_impl_members_inner");
let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?;
let impl_item_list = impl_node.item_list()?;
let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
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<SmolStr> {
let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
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<T> { fn foo(&self, t: T) -> &T; }
struct S;
impl Foo<u32> for S { <|> }",
"
impl Foo<u32> for S { <|> }"#,
r#"
trait Foo<T> { fn foo(&self, t: T) -> &T; }
struct S;
impl Foo<u32> for S {
<|>fn foo(&self, t: u32) -> &u32 { todo!() }
}",
fn foo(&self, t: u32) -> &u32 {
${0:todo!()}
}
}"#,
);
}
@ -294,16 +329,18 @@ impl Foo<u32> for S {
fn fill_in_type_params_2() {
check_assist(
add_missing_impl_members,
"
r#"
trait Foo<T> { fn foo(&self, t: T) -> &T; }
struct S;
impl<U> Foo<U> for S { <|> }",
"
impl<U> Foo<U> for S { <|> }"#,
r#"
trait Foo<T> { fn foo(&self, t: T) -> &T; }
struct S;
impl<U> Foo<U> for S {
<|>fn foo(&self, t: U) -> &U { todo!() }
}",
fn foo(&self, t: U) -> &U {
${0:todo!()}
}
}"#,
);
}
@ -311,16 +348,18 @@ impl<U> Foo<U> 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<T>;
trait Foo { fn foo(&self, bar: Bar<u32>); }
}
struct S;
impl foo::Foo for S { <|> }",
"
impl foo::Foo for S { <|> }"#,
r#"
mod foo {
pub struct Bar<T>;
trait Foo { fn foo(&self, bar: Bar<u32>); }
}
struct S;
impl foo::Foo for S {
<|>fn foo(&self, bar: foo::Bar<u32>) { todo!() }
}",
fn foo(&self, bar: foo::Bar<u32>) {
${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<T>;
trait Foo<T> { fn foo(&self, bar: Bar<T>); }
}
struct S;
impl foo::Foo<u32> for S { <|> }",
"
impl foo::Foo<u32> for S { <|> }"#,
r#"
mod foo {
pub struct Bar<T>;
trait Foo<T> { fn foo(&self, bar: Bar<T>); }
}
struct S;
impl foo::Foo<u32> for S {
<|>fn foo(&self, bar: foo::Bar<u32>) { todo!() }
}",
fn foo(&self, bar: foo::Bar<u32>) {
${0:todo!()}
}
}"#,
);
}
@ -398,15 +443,15 @@ impl foo::Foo<u32> for S {
// when substituting params, the substituted param should not be qualified!
check_assist(
add_missing_impl_members,
"
r#"
mod foo {
trait Foo<T> { fn foo(&self, bar: T); }
pub struct Param;
}
struct Param;
struct S;
impl foo::Foo<Param> for S { <|> }",
"
impl foo::Foo<Param> for S { <|> }"#,
r#"
mod foo {
trait Foo<T> { fn foo(&self, bar: T); }
pub struct Param;
@ -414,8 +459,10 @@ mod foo {
struct Param;
struct S;
impl foo::Foo<Param> for S {
<|>fn foo(&self, bar: Param) { todo!() }
}",
fn foo(&self, bar: Param) {
${0:todo!()}
}
}"#,
);
}
@ -423,15 +470,15 @@ impl foo::Foo<Param> for S {
fn test_qualify_path_associated_item() {
check_assist(
add_missing_impl_members,
"
r#"
mod foo {
pub struct Bar<T>;
impl Bar<T> { type Assoc = u32; }
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
}
struct S;
impl foo::Foo for S { <|> }",
"
impl foo::Foo for S { <|> }"#,
r#"
mod foo {
pub struct Bar<T>;
impl Bar<T> { type Assoc = u32; }
@ -439,8 +486,10 @@ mod foo {
}
struct S;
impl foo::Foo for S {
<|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { todo!() }
}",
fn foo(&self, bar: foo::Bar<u32>::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<T>;
pub struct Baz;
trait Foo { fn foo(&self, bar: Bar<Baz>); }
}
struct S;
impl foo::Foo for S { <|> }",
"
impl foo::Foo for S { <|> }"#,
r#"
mod foo {
pub struct Bar<T>;
pub struct Baz;
@ -464,8 +513,10 @@ mod foo {
}
struct S;
impl foo::Foo for S {
<|>fn foo(&self, bar: foo::Bar<foo::Baz>) { todo!() }
}",
fn foo(&self, bar: foo::Bar<foo::Baz>) {
${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<Args> { 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<Args> { 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<T = Self> {
fn bar(&self, other: &T);
}
struct S;
impl Foo for S { <|> }"#,
r#"
trait Foo<T = Self> {
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<T1, T2 = Self> {
fn bar(&self, this: &T1, that: &T2);
}
struct S<T>;
impl Foo<T> for S<T> { <|> }"#,
r#"
trait Foo<T1, T2 = Self> {
fn bar(&self, this: &T1, that: &T2);
}
struct S<T>;
impl Foo<T> for S<T> {
fn bar(&self, this: &T, that: &Self) {
${0:todo!()}
}
}"#,
)
}
}

View File

@ -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<T: Clone> Ctx<T> {
// fn new(data: T) -> Self { Self { data } }
// fn $0new(data: T) -> Self { Self { data } }
// }
//
// ```
pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
// 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<Assist> {
// 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<Assist> {
.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<Option<ast::ImplDef>> {
fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
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<Option<a
fn has_new_fn(imp: &ast::ImplDef) -> 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<T: Clone> {}
impl<T: Clone> Foo<T> {
fn new() -> Self { Self { } }<|>
fn $0new() -> Self { Self { } }
}
",
);
@ -212,7 +209,7 @@ impl<T: Clone> Foo<T> {
"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<i32> }
impl Foo {
fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|>
fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
}
",
);
@ -244,7 +241,7 @@ impl Foo {
"struct Foo { pub baz: String, pub qux: Vec<i32> }
impl Foo {
fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|>
fn $0new(baz: String, qux: Vec<i32>) -> 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<T> {
}
impl<T> Source<T> {
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<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
Source { file_id: self.file_id, ast: f(self.ast) }

View File

@ -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>() -> T { todo!() }
// fn main() {
// let x = make<|>();
// }
// ```
// ->
// ```
// fn make<T>() -> 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>() -> T {}
fn main() {
make<|>();
}
"#,
r#"
fn make<T>() -> T {}
fn main() {
make::<${0:_}>();
}
"#,
);
}
#[test]
fn add_turbo_fish_method() {
check_assist(
add_turbo_fish,
r#"
struct S;
impl S {
fn make<T>(&self) -> T {}
}
fn main() {
S.make<|>();
}
"#,
r#"
struct S;
impl S {
fn make<T>(&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>() -> 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<|>();
}
"#,
);
}
}

View File

@ -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<Assist> {
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
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<Assist> {
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]

View File

@ -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<Assist> {
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<Self> {
if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() {
fn new(ctx: &AssistContext) -> Option<Self> {
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
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<Self> {
fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
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<Self> {
fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
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<ModPath> {
@ -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<u8>,
test: PubStruct2<u8>,
}
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()
}
",
);

View File

@ -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<i32, ${0:_}> { Ok(42i32) }
// ```
pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
// 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<SyntaxNode>,
}
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<Vec<NodeType>> {
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<NodeType> = 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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<u32, ${0:_}> {
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<u32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<i32, ${0:_}> {
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<u32, ${0:_}> {
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<u32, ${0:_}> {
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<u32, ${0:_}> {
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<u32, ${0:_}> {
if the_field < 5 {
let mut i = 0;
if i == 5 {
return Ok(99);
} else {
return Ok(0);
}
}
Ok(the_field)
}"#,
);
}
}

View File

@ -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<Assist> {
pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
return change_vis(ctx, vis);
return change_vis(acc, vis);
}
add_vis(ctx)
add_vis(acc, ctx)
}
fn add_vis(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
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::<ast::Name>() {
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::<ast::TupleFieldDef>() {
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<Assist> {
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");

View File

@ -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<Assist> {
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<Assist> {
// 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<Assist> {
};
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<Assist> {
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<Assist> {
}
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<Assist> {
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<Assist> {
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<Assist> {
#[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<String>) {
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<String>) {
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,
};

View File

@ -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<Assist> {
pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
let match_arm_list = match_expr.match_arm_list()?;
@ -49,12 +53,18 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
let missing_arms: Vec<MatchArm> = 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::<Vec<_>>();
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<Assist> {
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<i32>},
Bs { x: i32, y: Option<i32> },
Cs(i32, Option<i32>),
}
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<i32>},
Bs { x: i32, y: Option<i32> },
Cs(i32, Option<i32>),
}
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<i32>),
}
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<i32>) {
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<i32>) {
match opt {
$0Some(_) => {}
None => {}
}
}
"#,
);
}
}

View File

@ -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<hir::Name>)> {
fn offset_target_and_file_id<S, Ast>(
db: &dyn HirDatabase,
x: S,
) -> (TextSize, TextRange, FileId)
where
S: HasSource<Ast = Ast>,
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
",
)
}
}

View File

@ -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<Assist> {
pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let expr = ctx.find_node_at_offset::<BinExpr>()?;
let lhs = expr.lhs()?.syntax().clone();
let rhs = expr.rhs()?.syntax().clone();
@ -33,8 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
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<BinOp> 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::<Self>() {
None => false,
Some(it) => self ==<|> it,
Some(it) => self == it,
}
}
"#,

View File

@ -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<Assist> {
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<Assist> {
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<Assist> {
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) {}",
)
}

View File

@ -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<T: Copy + Clone>() { }
// ```
pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
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<Assist> {
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<T> where T: A <|>+ B { }",
"struct S<T> where T: B <|>+ A { }",
"struct S<T> where T: B + A { }",
)
}
@ -69,13 +69,13 @@ mod tests {
check_assist(
flip_trait_bound,
"impl X for S<T> where T: A +<|> B { }",
"impl X for S<T> where T: B +<|> A { }",
"impl X for S<T> where T: B + A { }",
)
}
#[test]
fn flip_trait_bound_works_for_fn() {
check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }")
check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
}
#[test]
@ -83,7 +83,7 @@ mod tests {
check_assist(
flip_trait_bound,
"fn f<T>(t: T) where T: A +<|> B { }",
"fn f<T>(t: T) where T: B +<|> A { }",
"fn f<T>(t: T) where T: B + A { }",
)
}
@ -92,7 +92,7 @@ mod tests {
check_assist(
flip_trait_bound,
"fn f<T>(t: T) where T: A <|>+ 'static { }",
"fn f<T>(t: T) where T: 'static <|>+ A { }",
"fn f<T>(t: T) where T: 'static + A { }",
)
}
@ -101,7 +101,7 @@ mod tests {
check_assist(
flip_trait_bound,
"struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
"struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }",
"struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
)
}
@ -110,7 +110,7 @@ mod tests {
check_assist(
flip_trait_bound,
"struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
"struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }",
"struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
)
}
}

View File

@ -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<Assist> {
pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
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<Assist> {
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<Assist> {
| (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<Assist> {
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<usize> {
r"
fn foo() -> Option<usize> {
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"

View File

@ -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<Assist> {
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<Assist> {
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<Assist> {
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<ast::Expr> {
/// 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;
}
",

View File

@ -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<Assist> {
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<Assist> {
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 } }",
)
}

View File

@ -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<Assist> {
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<Assist> {
}
};
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};
",

View File

@ -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<Assist> {
pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
// 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<Assist> {
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<Assist> {
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<Assist> {
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!(),
}

View File

@ -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<Assist> {
pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
let mut type_params = type_param_list.type_params();
@ -49,7 +49,8 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
}
};
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<Assist> {
_ => 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<ast::WherePred> {
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 foo<T: u32, <|>F: FnOnce(T) -> T>() {}
"#,
r#"
fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {}
fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
"#,
);
}
@ -112,7 +112,7 @@ mod tests {
impl<U: u32, <|>T> A<U, T> {}
"#,
r#"
impl<U, <|>T> A<U, T> where U: u32 {}
impl<U, T> A<U, T> where U: u32 {}
"#,
);
}
@ -125,7 +125,7 @@ mod tests {
struct A<<|>T: Iterator<Item = u32>> {}
"#,
r#"
struct A<<|>T> where T: Iterator<Item = u32> {}
struct A<T> where T: Iterator<Item = u32> {}
"#,
);
}
@ -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, T) where T: u32;
"#,
);
}

View File

@ -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<Assist> {
pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
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<Assist> {
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<Assist> {
// }
// }
// ```
pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
let match_pat = match_arm.pat()?;
@ -108,14 +98,15 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
}
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<Assist> {
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
},

View File

@ -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<Assist> {
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<Assist> {
// "Hello, \"World!\"";
// }
// ```
pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
// r##"Hello, World!"##;
// }
// ```
pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
// r"Hello, World!";
// }
// ```
pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
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";
}
"##,
)

View File

@ -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<Assist> {
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
if !is_valid_macrocall(&macro_call, "dbg")? {
@ -29,26 +29,6 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
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) -> Option<b
#[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 test_remove_dbg() {
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, "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]

View File

@ -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<Assist> {
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<Assist> {
_ => 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));
})
}

View File

@ -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<Assist> {
reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx))
pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
}
fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let record = ctx.find_node_at_offset::<R>()?;
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<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
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<RootDatabase>) -> Option
}
}
fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> {
fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> {
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) -> Option<HashMap<String,
#[cfg(test)]
mod tests {
use crate::helpers::{check_assist, check_assist_not_applicable};
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
@ -149,7 +140,7 @@ mod tests {
"#,
r#"
struct Foo {foo: i32, bar: i32};
const test: Foo = <|>Foo {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",

View File

@ -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<Assist> {
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<Assist> {
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::<ast::Expr>(if_expr.into(), match_expr);
})
}
@ -74,13 +76,13 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
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<T> { Some(T), None }
use Option::*;
fn foo(x: Option<i32>) {
<|>match x {
match x {
Some(x) => println!("{}", x),
None => println!("none"),
}
@ -203,7 +205,7 @@ enum Result<T, E> { Ok(T), Err(E) }
use Result::*;
fn foo(x: Result<i32, ()>) {
<|>match x {
match x {
Ok(x) => println!("{}", x),
Err(_) => println!("none"),
}

View File

@ -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<i32> { None }
// ```
pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
)
.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<T> { X(T), Y(T) }
fn main() {
if let <|>x = E::X(92) {
if let x = E::X(92) {
}
}
",

View File

@ -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<String, String>) {}
// ```
pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> {
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<Assist>
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<Vec<SmolStr>> {
#[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
}
",
);

View File

@ -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<i32, i32> = Result::Ok(92);
// let y = match x {
// Ok(a) => a,
// _ => unreachable!(),
// $0_ => unreachable!(),
// };
// }
// ```
pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
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<Assist> {
}
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<Assist> {
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::<ast::Expr>(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<T, E> { Ok(T), Err(E) }
fn i<T>(a: T) -> T { a }
fn main() {
let x: Result<i32, i32> = 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<T> { Some(T), None }
fn i<T>(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<T, E> { Ok(T), Err(E) }
fn i<T>(a: T) -> T { a }
fn main() {
let x: Result<i32, i32> = Result::Ok(92);
let y = <|>match i(x) {
let y = match i(x) {
Ok(a) => a,
_ => unreachable!(),
$0_ => unreachable!(),
}.count_zeroes();
}
",

View File

@ -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<Assist> {
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<Assist> {
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}}",
)
}

View File

@ -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<ast::Expr> {
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::<Vec<String>>()
.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");
}
}
}
"#,
);
}
}

View File

@ -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<TextSize>,
// FIXME: This belongs to `AssistLabel`
pub target: Option<TextRange>,
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<GroupLabel>,
/// 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<GroupLabel>,
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<Assist> {
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<ResolvedAssist> {
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<GroupLabel>,
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<AssistLabel> {
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<ResolvedAssist> {
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::<Vec<_>>();
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 `<target> </target>` 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");
}
}

View File

@ -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
];

View File

@ -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 `<target> </target>` 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::<Vec<_>>()
.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");
}

View File

@ -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<T: Clone> {
data: T,<|>
data: T,<|>
}
"#####,
r#####"
struct Ctx<T: Clone> {
data: T,
data: T,
}
impl<T: Clone> Ctx<T> {
$0
}
"#####,
)
@ -125,7 +125,7 @@ impl<T: Clone> Ctx<T> {
#[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<T> {
@ -180,7 +180,9 @@ trait Trait<T> {
}
impl Trait<u32> for () {
fn foo(&self) -> u32 { todo!() }
fn foo(&self) -> u32 {
${0:todo!()}
}
}
"#####,
@ -189,7 +191,7 @@ impl Trait<u32> for () {
#[test]
fn doctest_add_new() {
check(
check_doc_test(
"add_new",
r#####"
struct Ctx<T: Clone> {
@ -202,16 +204,35 @@ struct Ctx<T: Clone> {
}
impl<T: Clone> Ctx<T> {
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>() -> T { todo!() }
fn main() {
let x = make<|>();
}
"#####,
r#####"
fn make<T>() -> 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<i32, ${0:_}> { 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<T: Clone +<|> Copy>() { }
@ -359,7 +416,7 @@ fn foo<T: Copy + Clone>() { }
#[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 apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
@ -530,7 +587,7 @@ fn apply<T, U, F>(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<T> { Some(T), None }
@ -677,7 +734,7 @@ fn compute() -> Option<i32> { 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<String, String>) {}
@ -692,7 +749,7 @@ fn process(map: HashMap<String, String>) {}
#[test]
fn doctest_replace_unwrap_with_match() {
check(
check_doc_test(
"replace_unwrap_with_match",
r#####"
enum Result<T, E> { Ok(T), Err(E) }
@ -707,7 +764,7 @@ fn main() {
let x: Result<i32, i32> = 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");
}
"#####,
)
}

View File

@ -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<usize> = 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<RootDatabase>,
impl_def: &ast::ImplDef,
) -> Vec<hir::AssocItem> {
@ -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<ast::Expr> {
}
#[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<RootDatabase>, ty: &Type) -> Option<TryEnum> {
pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
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<T> {
fn from(T) -> Self;
}
}
pub mod prelude { pub use crate::convert::From }
pub mod option {
pub enum Option<T> { 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<Enum> {
self.find_enum("core:option:Option")
}
fn find_trait(&self, path: &str) -> Option<Trait> {
match self.find_def(path)? {
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
_ => None,
}
}
fn find_enum(&self, path: &str) -> Option<Enum> {
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<ScopeDef> {
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)
}
}

View File

@ -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::<Vec<_>>();
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);
}
}

View File

@ -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<Item = SmolStr>) {
iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat));
}
}

View File

@ -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;

View File

@ -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" }

View File

@ -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<DiagnosticSeverity> {
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<Location> {
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<DiagnosticRelatedInformation> {
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<Url, Vec<TextEdit>> = 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<CodeAction>,
}
/// 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<MappedRustDiagnostic> {
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<Path>,
) -> Result<Url, Box<dyn std::error::Error + Send + Sync>> {
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");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<String> },
CargoCommand { command: String, all_targets: bool, all_features: bool, extra_args: Vec<String> },
CustomCommand { command: String, args: Vec<String> },
}
@ -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<CodeActionOrCommand> },
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<CheckTask>) {
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<CheckTask>) {
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<CodeAction>,
}
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::<cargo_metadata::Message>(&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())
}

View File

@ -42,7 +42,6 @@ pub fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
}
pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
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() {

View File

@ -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<Visibility> {
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<Name> {
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<Type> {
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<T>(
pub fn iterate_assoc_items<T>(
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)
}
}

View File

@ -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<PathResolution> {
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<PathResolution> {
resolve_hir_path_qualifier(self.db, &self.resolver, path)
}
}
// FIXME: Change `HasSource` trait to work with `Semantics` and remove this?

View File

@ -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<ChildContainer> {

View File

@ -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<PathResolution> {
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()),
})
}

View File

@ -117,7 +117,14 @@ fn lower_enum(
ast: &InFile<ast::EnumDef>,
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;
}

View File

@ -140,6 +140,7 @@ impl Attr {
}
}
#[derive(Debug, Clone, Copy)]
pub struct AttrQuery<'a> {
attrs: &'a Attrs,
key: &'static str,

View File

@ -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<Path> {

View File

@ -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<ast::ParamList>,
body: Option<ast::Expr>,
) -> (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<ast::Expr>) -> 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<ast::Name>) = 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),
);
}
}
}

View File

@ -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() {

View File

@ -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<TraitData> {
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<AssocItemId> = 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<ConstData> {
let node = konst.lookup(db).source(db);
Arc::new(ConstData::new(db, RawVisibility::private(), node))
}
fn new<N: NameOwner + TypeAscriptionOwner + VisibilityOwner>(
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<Name>,
pub type_ref: TypeRef,
pub visibility: RawVisibility,
pub mutable: bool,
}
impl StaticData {
pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc<StaticData> {
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<ast::ItemList>,
id: ImplId,
) -> Vec<AssocItemId> {
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<AssocItemId> {
) -> Vec<(Name, AssocItemId)> {
if limit == 0 {
return Vec::new();
}
if let Some((mark, items)) = expander.enter_expand(db, None, m) {
let items: InFile<ast::MacroItems> = 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<Item = ImplItem>,
assoc_items: impl Iterator<Item = AssocItem>,
file_id: crate::HirFileId,
id: ImplId,
) -> Vec<AssocItemId> {
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()

View File

@ -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<dyn AstDatabase> {
#[salsa::invoke(ConstData::const_data_query)]
fn const_data(&self, konst: ConstId) -> Arc<ConstData>;
#[salsa::invoke(ConstData::static_data_query)]
fn static_data(&self, konst: StaticId) -> Arc<ConstData>;
#[salsa::invoke(StaticData::static_data_query)]
fn static_data(&self, konst: StaticId) -> Arc<StaticData>;
#[salsa::invoke(Body::body_with_source_map_query)]
fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc<Body>, Arc<BodySourceMap>);
@ -108,6 +112,16 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
// Remove this query completely, in favor of `Attrs::docs` method
#[salsa::invoke(Documentation::documentation_query)]
fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
#[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<ModPath>;
}
fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {

View File

@ -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);

View File

@ -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<ModPath> {
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<ModPath> {
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#"

View File

@ -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),

View File

@ -46,8 +46,6 @@ pub mod find_path;
#[cfg(test)]
mod test_db;
#[cfg(test)]
mod marks;
use std::hash::Hash;

View File

@ -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
);

View File

@ -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);
}

View File

@ -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) => {

View File

@ -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<ast::FnDef>),
Struct(FileAstId<ast::StructDef>),
Struct(FileAstId<ast::StructDef>, StructDefKind),
Union(FileAstId<ast::UnionDef>),
Enum(FileAstId<ast::EnumDef>),
Const(FileAstId<ast::ConstDef>),
@ -171,7 +178,7 @@ impl DefKind {
pub fn ast_id(&self) -> FileAstId<ast::ModuleItem> {
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<Name>,
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<Idx<ModuleData>>, 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));
}

View File

@ -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

View File

@ -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

View File

@ -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#"

View File

@ -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

View File

@ -116,6 +116,21 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
}
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 });

View File

@ -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)
}

View File

@ -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(

View File

@ -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 })
}
};
}

View File

@ -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(&macro_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(&macro_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]

View File

@ -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<tt::Subtree, mbe::ExpandError>;
}
/// 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<tt::Subtree, mbe::ExpandError> {
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, &macro_arg.0)
}
pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> {
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,

View File

@ -16,31 +16,34 @@ use crate::{
pub struct Hygiene {
// This is what `$crate` expands to
def_crate: Option<CrateId>,
// 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<CrateId> {
if self.local_inner {
self.def_crate
} else {
None
}
}
}

View File

@ -204,6 +204,8 @@ pub struct MacroDefId {
pub krate: Option<CrateId>,
pub ast_id: Option<AstId<ast::MacroCall>>,
pub kind: MacroDefKind,
pub local_inner: bool,
}
impl MacroDefId {

View File

@ -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"

View File

@ -573,14 +573,20 @@ pub(crate) fn is_useful(
matrix: &Matrix,
v: &PatStack,
) -> MatchCheckResult<Usefulness> {
// 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]

View File

@ -131,3 +131,31 @@ impl AstDiagnostic for MissingOkInTailExpr {
ast::Expr::cast(node).unwrap()
}
}
#[derive(Debug)]
pub struct BreakOutsideOfLoop {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for BreakOutsideOfLoop {
fn message(&self) -> String {
"break outside of loop".to_string()
}
fn source(&self) -> InFile<SyntaxNodePtr> {
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()
}
}

View File

@ -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<usize>,
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<String, DisplaySourceCodeError> {
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<T: HirDisplay>(
&mut self,
iter: impl IntoIterator<Item = T>,
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<usize>, 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<fmt::Error> for HirDisplayError {
fn from(_: fmt::Error) -> Self {
Self::FmtError
}
}
pub struct HirDisplayWrapper<'a, T> {
db: &'a dyn HirDatabase,
t: &'a T,
max_size: Option<usize>,
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)
),
}
)?,
})
}
}

View File

@ -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<Infer
match def {
DefWithBodyId::ConstId(c) => 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<BreakableContext>,
}
#[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<VariantId>),
unresolved: Option<usize>,
) -> (Ty, Option<VariantId>) {
if unresolved.is_none() {
result
} else {
// FIXME diagnostic
(Ty::Unknown, None)
}
}
fn ty_variant(ty: &Ty) -> Option<VariantId> {
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 })
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<ExprId>,
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(

View File

@ -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;

View File

@ -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<Substs>)> {
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())))
}
}

View File

@ -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();
}

View File

@ -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<usize> {
generics(db.upcast(), id.parent).param_idx(id)
}
#[derive(Debug, Clone)]
pub struct SubstsBuilder {
vec: Vec<Ty>,
@ -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<TraitId> {
/// If this is a `dyn Trait`, returns that trait.
pub fn dyn_trait(&self) -> Option<TraitId> {
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,
}
}

View File

@ -812,7 +812,7 @@ impl TraitEnvironment {
// add `Self: Trait<T1, T2, ...>` 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);

View File

@ -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
);

Some files were not shown because too many files have changed in this diff Show More