mirror of
https://github.com/rust-lang/rust.git
synced 2024-12-14 01:25:54 +00:00
Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into modname_spacing
This commit is contained in:
commit
7fece3bdd2
16
.github/workflows/release.yaml
vendored
16
.github/workflows/release.yaml
vendored
@ -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
201
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
27
crates/ra_assists/src/assist_config.rs
Normal file
27
crates/ra_assists/src/assist_config.rs
Normal 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: () }) }
|
||||
}
|
||||
}
|
257
crates/ra_assists/src/assist_context.rs
Normal file
257
crates/ra_assists/src/assist_context.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
||||
",
|
||||
)
|
||||
|
@ -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, }
|
||||
",
|
||||
);
|
||||
|
@ -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 };
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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}",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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!()}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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) }
|
||||
|
134
crates/ra_assists/src/handlers/add_turbo_fish.rs
Normal file
134
crates/ra_assists/src/handlers/add_turbo_fish.rs
Normal 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<|>();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -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()
|
||||
}
|
||||
",
|
||||
);
|
||||
|
961
crates/ra_assists/src/handlers/change_return_type_to_result.rs
Normal file
961
crates/ra_assists/src/handlers/change_return_type_to_result.rs
Normal 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)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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 => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
559
crates/ra_assists/src/handlers/fix_visibility.rs
Normal file
559
crates/ra_assists/src/handlers/fix_visibility.rs
Normal 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
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
|
@ -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) {}",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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 { }",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
",
|
||||
|
@ -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 } }",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
",
|
||||
|
@ -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!(),
|
||||
}
|
||||
|
@ -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;
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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";
|
||||
}
|
||||
"##,
|
||||
)
|
||||
|
@ -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(¯o_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]
|
||||
|
@ -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));
|
||||
})
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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"),
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
",
|
||||
|
@ -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
|
||||
}
|
||||
",
|
||||
);
|
||||
|
@ -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();
|
||||
}
|
||||
",
|
||||
|
@ -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}}",
|
||||
)
|
||||
}
|
||||
|
||||
|
512
crates/ra_assists/src/handlers/unwrap_block.rs
Normal file
512
crates/ra_assists/src/handlers/unwrap_block.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
];
|
153
crates/ra_assists/src/tests.rs
Normal file
153
crates/ra_assists/src/tests.rs
Normal 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");
|
||||
}
|
@ -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");
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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" }
|
||||
|
@ -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
@ -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())
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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> {
|
||||
|
@ -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()),
|
||||
})
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -140,6 +140,7 @@ impl Attr {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AttrQuery<'a> {
|
||||
attrs: &'a Attrs,
|
||||
key: &'static str,
|
||||
|
@ -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> {
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
|
@ -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#"
|
||||
|
@ -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),
|
||||
|
@ -46,8 +46,6 @@ pub mod find_path;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_db;
|
||||
#[cfg(test)]
|
||||
mod marks;
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
|
@ -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
|
||||
);
|
@ -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);
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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#"
|
||||
|
@ -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
|
||||
|
@ -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 });
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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 })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -73,11 +73,13 @@ pub fn find_builtin_macro(
|
||||
krate: Some(krate),
|
||||
ast_id: Some(ast_id),
|
||||
kind: MacroDefKind::BuiltIn(kind),
|
||||
local_inner: false,
|
||||
}),
|
||||
Either::Right(kind) => Some(MacroDefId {
|
||||
krate: Some(krate),
|
||||
ast_id: Some(ast_id),
|
||||
kind: MacroDefKind::BuiltInEager(kind),
|
||||
local_inner: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -358,7 +360,7 @@ fn env_expand(
|
||||
// However, we cannot use an empty string here, because for
|
||||
// `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
|
||||
// `include!("foo.rs"), which might go to infinite loop
|
||||
let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTATED__".to_string());
|
||||
let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string());
|
||||
let expanded = quote! { #s };
|
||||
|
||||
Ok((expanded, FragmentKind::Expr))
|
||||
@ -406,6 +408,7 @@ mod tests {
|
||||
krate: Some(CrateId(0)),
|
||||
ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))),
|
||||
kind: MacroDefKind::BuiltIn(expander),
|
||||
local_inner: false,
|
||||
};
|
||||
|
||||
let loc = MacroCallLoc {
|
||||
@ -425,6 +428,7 @@ mod tests {
|
||||
krate: Some(CrateId(0)),
|
||||
ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))),
|
||||
kind: MacroDefKind::BuiltInEager(expander),
|
||||
local_inner: false,
|
||||
};
|
||||
|
||||
let args = macro_calls[1].token_tree().unwrap();
|
||||
@ -504,7 +508,7 @@ mod tests {
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(expanded, "\"__RA_UNIMPLEMENTATED__\"");
|
||||
assert_eq!(expanded, "\"__RA_UNIMPLEMENTED__\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -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, ¯o_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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
),
|
||||
}
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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())))
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user