diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1563ee0b14..bb77324378 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -43,14 +43,31 @@ jobs: rustup component add rustfmt rust-src - name: Cache Dependencies - uses: Swatinem/rust-cache@ce325b60658c1b38465c06cc965b79baf32c1e72 + uses: Swatinem/rust-cache@76686c56f2b581d1bb5bda44b51f7e24bd9b8b8e - - name: Compile + - name: Bump opt-level + if: matrix.os == 'ubuntu-latest' + run: sed -i '/\[profile.dev]/a opt-level=1' Cargo.toml + + - name: Compile (tests) run: cargo test --no-run --locked + # It's faster to `test` before `build` ¯\_(ツ)_/¯ + - name: Compile (rust-analyzer) + if: matrix.os == 'ubuntu-latest' + run: cargo build --quiet + - name: Test run: cargo test -- --nocapture --quiet + - name: Run analysis-stats on rust-analyzer + if: matrix.os == 'ubuntu-latest' + run: target/${{ matrix.target }}/debug/rust-analyzer analysis-stats . + + - name: Run analysis-stats on rust std library + if: matrix.os == 'ubuntu-latest' + run: target/${{ matrix.target }}/debug/rust-analyzer analysis-stats --with-deps $(rustc --print sysroot)/lib/rustlib/src/rust/library/std + # Weird targets to catch non-portable code rust-cross: if: github.repository == 'rust-lang/rust-analyzer' @@ -73,7 +90,7 @@ jobs: rustup target add ${{ env.targets }} ${{ env.targets_ide }} - name: Cache Dependencies - uses: Swatinem/rust-cache@ce325b60658c1b38465c06cc965b79baf32c1e72 + uses: Swatinem/rust-cache@76686c56f2b581d1bb5bda44b51f7e24bd9b8b8e - name: Check run: | @@ -99,9 +116,9 @@ jobs: uses: actions/checkout@v3 - name: Install Nodejs - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 16.x + node-version: 16 - name: Install xvfb if: matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f4d472e3d5..422fe29f9d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -76,9 +76,9 @@ jobs: rustup component add rust-src - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 16.x + node-version: 16 - name: Update apt repositories if: matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'arm-unknown-linux-gnueabihf' @@ -184,9 +184,9 @@ jobs: needs: ["dist", "dist-x86_64-unknown-linux-musl"] steps: - name: Install Nodejs - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 16.x + node-version: 16 - run: echo "TAG=$(date --iso -u)" >> $GITHUB_ENV if: github.ref == 'refs/heads/release' @@ -259,6 +259,7 @@ jobs: working-directory: ./editors/code # token from https://dev.azure.com/rust-analyzer/ run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix || true + timeout-minutes: 2 - name: Publish Extension (Code Marketplace, nightly) if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') @@ -269,3 +270,4 @@ jobs: if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') working-directory: ./editors/code run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix || true + timeout-minutes: 2 diff --git a/Cargo.lock b/Cargo.lock index 216cf51447..0ddea2f728 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "anymap" @@ -49,9 +49,9 @@ checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" [[package]] name = "arbitrary" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" +checksum = "d86fd10d912cab78764cc44307d9cd5f164e09abbeb87fb19fb6d95937e8da5f" [[package]] name = "arrayvec" @@ -171,9 +171,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chalk-derive" -version = "0.84.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf29c109d57f8d57b0e7675391be37a9285d86dd93278bd5f14a0ad3c447a6c2" +checksum = "5499d415d855b5094366a824815341893ad3de0ecb6048c430118bdae6d27402" dependencies = [ "proc-macro2", "quote", @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "chalk-ir" -version = "0.84.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d391763027b5e50a5e15caf6d2857ec585fd68160367bbeac9e1804209620918" +checksum = "3800118c76a48507b0eece3a01f3a429b5c478d203c493096e6040c67ab960e1" dependencies = [ "bitflags", "chalk-derive", @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "chalk-recursive" -version = "0.84.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afafd92dcdc7fe0ea940ee94bdd8cc5bd18f4a4a84c593d6d7025fe16c150478" +checksum = "1baf60628fd73104d1f8562586a52d48f37f1e84435aab2e62674b1fd935b8c8" dependencies = [ "chalk-derive", "chalk-ir", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "chalk-solve" -version = "0.84.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af1d111f11c91c48ace02e93e470c5bae6d2631bd112e4545317da53660d7fc" +checksum = "0e9c3c068f9358786348e58a1b94ef0a5cf90a9810fc1f10fda896f0b5d80185" dependencies = [ "chalk-derive", "chalk-ir", @@ -270,45 +270,44 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "dashmap" -version = "5.3.4" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if", "hashbrown", "lock_api", + "once_cell", "parking_lot_core 0.9.3", ] [[package]] name = "derive_arbitrary" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d" +checksum = "226ad66541d865d7a7173ad6a9e691c33fdb910ac723f4bc734b3e5294a1f931" dependencies = [ "proc-macro2", "quote", @@ -404,11 +403,10 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -546,6 +544,7 @@ version = "0.0.0" dependencies = [ "arrayvec", "base-db", + "chalk-derive", "chalk-ir", "chalk-recursive", "chalk-solve", @@ -573,9 +572,9 @@ dependencies = [ [[package]] name = "home" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" dependencies = [ "winapi", ] @@ -714,11 +713,10 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -764,18 +762,18 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jod-thread" @@ -815,9 +813,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libloading" @@ -831,9 +829,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ca136052550448f55df7898c6dbe651c6b574fe38a0d9ea687a9f8088a2e2c" +checksum = "8fc093ab289b0bfda3aa1bdfab9c9542be29c7ef385cfcbe77f8c9813588eb48" dependencies = [ "cc", ] @@ -844,9 +842,9 @@ version = "0.0.0" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -863,7 +861,7 @@ dependencies = [ [[package]] name = "lsp-server" -version = "0.6.0" +version = "0.7.0" dependencies = [ "crossbeam-channel", "log", @@ -894,12 +892,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "mbe" version = "0.0.0" @@ -941,18 +933,18 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f64ad83c969af2e732e907564deb0d0ed393cec4af80776f77dd77a1a427698" +checksum = "76ce6a4b40d3bff9eb3ce9881ca0737a85072f9f975886082640cd46a75cdb35" dependencies = [ "libmimalloc-sys", ] [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -980,9 +972,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0-pre.16" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "530f6314d6904508082f4ea424a0275cf62d341e118b313663f266429cb19693" +checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" dependencies = [ "bitflags", "crossbeam-channel", @@ -1017,9 +1009,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "oorandom" @@ -1088,9 +1080,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "paths" @@ -1098,9 +1090,9 @@ version = "0.0.0" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "perf-event" @@ -1190,9 +1182,9 @@ version = "0.0.0" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -1265,9 +1257,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "10.0.2" +version = "10.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1353ac408192fa925228d3e60ff746167d03f4f7e54835d78ef79e08225d913" +checksum = "0194e6e1966c23cc5fd988714f85b18d548d773e81965413555d96569931833d" dependencies = [ "pulldown-cmark", ] @@ -1340,9 +1332,9 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "rowan" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88acf7b001007e9e8c989fe7449f6601d909e5dd2c56399fc158977ad6c56e8" +checksum = "5811547e7ba31e903fe48c8ceab10d40d70a101f3d15523c847cce91aa71f332" dependencies = [ "countme", "hashbrown", @@ -1493,27 +1485,27 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -1522,9 +1514,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "indexmap", "itoa", @@ -1554,9 +1546,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smol_str" @@ -1593,9 +1585,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.99" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -1666,18 +1658,18 @@ checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -1715,9 +1707,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.5.1+5.3.0-patched" +version = "0.5.2+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931e876f91fed0827f863a2d153897790da0b24d882c721a79cb3beb0b903261" +checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" dependencies = [ "cc", "fs_extra", @@ -1758,9 +1750,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -1770,9 +1762,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1781,9 +1773,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -1802,9 +1794,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ "matchers", "once_cell", @@ -1866,40 +1858,39 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", "serde", ] @@ -2082,18 +2073,18 @@ checksum = "06069a848f95fceae3e5e03c0ddc8cb78452b56654ee0c8e68f938cf790fb9e3" [[package]] name = "xflags" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f14fe1ed41a5a2b5ef3f565586c4a8a559ee55d3953faab360a771135bdee00" +checksum = "cbf19f5031a1a812e96fede16f8161218883079946cea87619d3613db1efd268" dependencies = [ "xflags-macros", ] [[package]] name = "xflags-macros" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d11d5fc2a97287eded8b170ca80533b3c42646dd7fa386a5eb045817921022" +checksum = "2afbd7f2039bb6cad2dd45f0c5dff49c0d4e26118398768b7a605524d4251809" [[package]] name = "xshell" diff --git a/Cargo.toml b/Cargo.toml index 6b68ca8238..286ef1e7dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ debug = 0 # chalk-solve = { path = "../chalk/chalk-solve" } # chalk-ir = { path = "../chalk/chalk-ir" } # chalk-recursive = { path = "../chalk/chalk-recursive" } +# chalk-derive = { path = "../chalk/chalk-derive" } # ungrammar = { path = "../ungrammar" } diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs index 8e6e6a11ab..5b7828a269 100644 --- a/crates/base-db/src/fixture.rs +++ b/crates/base-db/src/fixture.rs @@ -196,7 +196,7 @@ impl ChangeFixture { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); } else { for (from, to, prelude) in crate_deps { @@ -270,7 +270,7 @@ impl ChangeFixture { Env::default(), Ok(proc_macro), true, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); for krate in all_crates { @@ -398,7 +398,7 @@ fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option) { let (version, origin) = match b.split_once(':') { Some(("CratesIo", data)) => match data.split_once(',') { Some((version, url)) => { - (version, CrateOrigin::CratesIo { repo: Some(url.to_owned()) }) + (version, CrateOrigin::CratesIo { repo: Some(url.to_owned()), name: None }) } _ => panic!("Bad crates.io parameter: {}", data), }, @@ -409,7 +409,7 @@ fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option) { let crate_origin = match &*crate_str { "std" => CrateOrigin::Lang(LangCrateOrigin::Std), "core" => CrateOrigin::Lang(LangCrateOrigin::Core), - _ => CrateOrigin::CratesIo { repo: None }, + _ => CrateOrigin::CratesIo { repo: None, name: None }, }; (crate_str, crate_origin, None) } diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index b388e47dee..e7f0c4ec29 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -136,7 +136,7 @@ impl ops::Deref for CrateName { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CrateOrigin { /// Crates that are from crates.io official registry, - CratesIo { repo: Option }, + CratesIo { repo: Option, name: Option }, /// Crates that are provided by the language, like std, core, proc-macro, ... Lang(LangCrateOrigin), } @@ -648,7 +648,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -660,7 +660,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -672,7 +672,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -698,7 +698,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -710,7 +710,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -733,7 +733,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -745,7 +745,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -757,7 +757,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -780,7 +780,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -792,7 +792,7 @@ mod tests { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); assert!(graph .add_dep( diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml index c9664a83ab..ee1ad677a9 100644 --- a/crates/cfg/Cargo.toml +++ b/crates/cfg/Cargo.toml @@ -22,5 +22,5 @@ oorandom = "11.1.3" # We depend on both individually instead of using `features = ["derive"]` to microoptimize the # build graph: if the feature was enabled, syn would be built early on in the graph if `smolstr` # supports `arbitrary`. This way, we avoid feature unification. -arbitrary = "1.1.0" -derive_arbitrary = "1.1.0" +arbitrary = "1.1.7" +derive_arbitrary = "1.1.6" diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml index 688e790c53..2ad32d2483 100644 --- a/crates/flycheck/Cargo.toml +++ b/crates/flycheck/Cargo.toml @@ -11,11 +11,11 @@ doctest = false [dependencies] crossbeam-channel = "0.5.5" -tracing = "0.1.35" +tracing = "0.1.37" cargo_metadata = "0.15.0" rustc-hash = "1.1.0" serde = { version = "1.0.137", features = ["derive"] } -serde_json = "1.0.81" +serde_json = "1.0.86" jod-thread = "0.1.2" toolchain = { path = "../toolchain", version = "0.0.0" } diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index fdc03f4053..e8c63d410a 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -169,13 +169,17 @@ impl FlycheckActor { } fn next_event(&self, inbox: &Receiver) -> Option { let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); + if let Ok(msg) = inbox.try_recv() { + // give restarts a preference so check outputs don't block a restart or stop + return Some(Event::Restart(msg)); + } select! { recv(inbox) -> msg => msg.ok().map(Event::Restart), recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), } } fn run(mut self, inbox: Receiver) { - while let Some(event) = self.next_event(&inbox) { + 'event: while let Some(event) = self.next_event(&inbox) { match event { Event::Restart(Restart::No) => { self.cancel_check_process(); @@ -183,7 +187,12 @@ impl FlycheckActor { Event::Restart(Restart::Yes) => { // Cancel the previously spawned process self.cancel_check_process(); - while let Ok(_) = inbox.recv_timeout(Duration::from_millis(50)) {} + while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { + // restart chained with a stop, so just cancel + if let Restart::No = restart { + continue 'event; + } + } let command = self.check_command(); tracing::debug!(?command, "will restart flycheck"); diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index e8cff2f3e6..4ad8e75970 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -15,17 +15,17 @@ arrayvec = "0.7.2" bitflags = "1.3.2" cov-mark = "2.0.0-pre.1" # We need to freeze the version of the crate, as the raw-api feature is considered unstable -dashmap = { version = "=5.3.4", features = ["raw-api"] } +dashmap = { version = "=5.4.0", features = ["raw-api"] } drop_bomb = "0.1.5" either = "1.7.0" fst = { version = "0.4.7", default-features = false } hashbrown = { version = "0.12.1", default-features = false } indexmap = "1.9.1" -itertools = "0.10.3" +itertools = "0.10.5" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } -once_cell = "1.12.0" +once_cell = "1.15.0" rustc-hash = "1.1.0" -smallvec = "1.9.0" +smallvec = "1.10.0" tracing = "0.1.35" stdx = { path = "../stdx", version = "0.0.0" } diff --git a/crates/hir-def/src/adt.rs b/crates/hir-def/src/adt.rs index 277135d6dc..938db032fb 100644 --- a/crates/hir-def/src/adt.rs +++ b/crates/hir-def/src/adt.rs @@ -1,12 +1,12 @@ //! Defines hir-level representation of structs, enums and unions -use std::sync::Arc; +use std::{num::NonZeroU32, sync::Arc}; use base_db::CrateId; use either::Either; use hir_expand::{ name::{AsName, Name}, - InFile, + HirFileId, InFile, }; use la_arena::{Arena, ArenaMap}; use syntax::ast::{self, HasName, HasVisibility}; @@ -14,15 +14,18 @@ use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}; use crate::{ body::{CfgExpander, LowerCtx}, + builtin_type::{BuiltinInt, BuiltinUint}, db::DefDatabase, intern::Interned, - item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId}, + item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId}, + nameres::diagnostics::DefDiagnostic, src::HasChildSource, src::HasSource, trace::Trace, type_ref::TypeRef, visibility::RawVisibility, - EnumId, LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StructId, UnionId, VariantId, + EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId, + VariantId, }; use cfg::CfgOptions; @@ -31,7 +34,7 @@ use cfg::CfgOptions; pub struct StructData { pub name: Name, pub variant_data: Arc, - pub repr: Option, + pub repr: Option, pub visibility: RawVisibility, } @@ -39,6 +42,7 @@ pub struct StructData { pub struct EnumData { pub name: Name, pub variants: Arena, + pub repr: Option, pub visibility: RawVisibility, } @@ -63,10 +67,19 @@ pub struct FieldData { pub visibility: RawVisibility, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Copy, Debug, Clone, PartialEq, Eq)] pub enum ReprKind { - Packed, - Other, + C, + BuiltinInt { builtin: Either, is_c: bool }, + Transparent, + Default, +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub struct ReprData { + pub kind: ReprKind, + pub packed: bool, + pub align: Option, } fn repr_from_value( @@ -74,25 +87,71 @@ fn repr_from_value( krate: CrateId, item_tree: &ItemTree, of: AttrOwner, -) -> Option { +) -> Option { item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt) } -fn parse_repr_tt(tt: &Subtree) -> Option { +fn parse_repr_tt(tt: &Subtree) -> Option { match tt.delimiter { Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {} _ => return None, } - let mut it = tt.token_trees.iter(); - match it.next()? { - TokenTree::Leaf(Leaf::Ident(ident)) if ident.text == "packed" => Some(ReprKind::Packed), - _ => Some(ReprKind::Other), + let mut data = ReprData { kind: ReprKind::Default, packed: false, align: None }; + + let mut tts = tt.token_trees.iter().peekable(); + while let Some(tt) = tts.next() { + if let TokenTree::Leaf(Leaf::Ident(ident)) = tt { + match &*ident.text { + "packed" => { + data.packed = true; + if let Some(TokenTree::Subtree(_)) = tts.peek() { + tts.next(); + } + } + "align" => { + if let Some(TokenTree::Subtree(tt)) = tts.peek() { + tts.next(); + if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() { + if let Ok(align) = lit.text.parse() { + data.align = Some(align); + } + } + } + } + "C" => { + if let ReprKind::BuiltinInt { is_c, .. } = &mut data.kind { + *is_c = true; + } else { + data.kind = ReprKind::C; + } + } + "transparent" => data.kind = ReprKind::Transparent, + repr => { + let is_c = matches!(data.kind, ReprKind::C); + if let Some(builtin) = BuiltinInt::from_suffix(repr) + .map(Either::Left) + .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right)) + { + data.kind = ReprKind::BuiltinInt { builtin, is_c }; + } + } + } + } } + + Some(data) } impl StructData { pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc { + db.struct_data_with_diagnostics(id).0 + } + + pub(crate) fn struct_data_with_diagnostics_query( + db: &dyn DefDatabase, + id: StructId, + ) -> (Arc, Arc<[DefDiagnostic]>) { let loc = id.lookup(db); let krate = loc.container.krate; let item_tree = loc.id.item_tree(db); @@ -100,15 +159,35 @@ impl StructData { let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); let strukt = &item_tree[loc.id.value]; - let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &strukt.fields, None); - Arc::new(StructData { - name: strukt.name.clone(), - variant_data: Arc::new(variant_data), - repr, - visibility: item_tree[strukt.visibility].clone(), - }) + let (variant_data, diagnostics) = lower_fields( + db, + krate, + loc.id.file_id(), + loc.container.local_id, + &item_tree, + &cfg_options, + &strukt.fields, + None, + ); + ( + Arc::new(StructData { + name: strukt.name.clone(), + variant_data: Arc::new(variant_data), + repr, + visibility: item_tree[strukt.visibility].clone(), + }), + diagnostics.into(), + ) } + pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc { + db.union_data_with_diagnostics(id).0 + } + + pub(crate) fn union_data_with_diagnostics_query( + db: &dyn DefDatabase, + id: UnionId, + ) -> (Arc, Arc<[DefDiagnostic]>) { let loc = id.lookup(db); let krate = loc.container.krate; let item_tree = loc.id.item_tree(db); @@ -116,56 +195,98 @@ impl StructData { let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); let union = &item_tree[loc.id.value]; - let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &union.fields, None); - - Arc::new(StructData { - name: union.name.clone(), - variant_data: Arc::new(variant_data), - repr, - visibility: item_tree[union.visibility].clone(), - }) + let (variant_data, diagnostics) = lower_fields( + db, + krate, + loc.id.file_id(), + loc.container.local_id, + &item_tree, + &cfg_options, + &union.fields, + None, + ); + ( + Arc::new(StructData { + name: union.name.clone(), + variant_data: Arc::new(variant_data), + repr, + visibility: item_tree[union.visibility].clone(), + }), + diagnostics.into(), + ) } } impl EnumData { pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc { + db.enum_data_with_diagnostics(e).0 + } + + pub(crate) fn enum_data_with_diagnostics_query( + db: &dyn DefDatabase, + e: EnumId, + ) -> (Arc, Arc<[DefDiagnostic]>) { let loc = e.lookup(db); let krate = loc.container.krate; let item_tree = loc.id.item_tree(db); let cfg_options = db.crate_graph()[krate].cfg_options.clone(); + let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); let enum_ = &item_tree[loc.id.value]; let mut variants = Arena::new(); + let mut diagnostics = Vec::new(); for tree_id in enum_.variants.clone() { - if item_tree.attrs(db, krate, tree_id.into()).is_cfg_enabled(&cfg_options) { - let var = &item_tree[tree_id]; - let var_data = lower_fields( + let attrs = item_tree.attrs(db, krate, tree_id.into()); + let var = &item_tree[tree_id]; + if attrs.is_cfg_enabled(&cfg_options) { + let (var_data, field_diagnostics) = lower_fields( db, krate, + loc.id.file_id(), + loc.container.local_id, &item_tree, &cfg_options, &var.fields, Some(enum_.visibility), ); + diagnostics.extend(field_diagnostics); variants.alloc(EnumVariantData { name: var.name.clone(), variant_data: Arc::new(var_data), }); + } else { + diagnostics.push(DefDiagnostic::unconfigured_code( + loc.container.local_id, + InFile::new(loc.id.file_id(), var.ast_id.upcast()), + attrs.cfg().unwrap(), + cfg_options.clone(), + )) } } - Arc::new(EnumData { - name: enum_.name.clone(), - variants, - visibility: item_tree[enum_.visibility].clone(), - }) + ( + Arc::new(EnumData { + name: enum_.name.clone(), + variants, + repr, + visibility: item_tree[enum_.visibility].clone(), + }), + diagnostics.into(), + ) } pub fn variant(&self, name: &Name) -> Option { let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?; Some(id) } + + pub fn variant_body_type(&self) -> Either { + match self.repr { + Some(ReprData { kind: ReprKind::BuiltinInt { builtin, .. }, .. }) => builtin, + _ => Either::Left(BuiltinInt::Isize), + } + } } impl HasChildSource for EnumId { @@ -324,31 +445,64 @@ fn lower_struct( fn lower_fields( db: &dyn DefDatabase, krate: CrateId, + current_file_id: HirFileId, + container: LocalModuleId, item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields, override_visibility: Option, -) -> VariantData { +) -> (VariantData, Vec) { + let mut diagnostics = Vec::new(); match fields { Fields::Record(flds) => { let mut arena = Arena::new(); for field_id in flds.clone() { - if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) { - arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility)); + let attrs = item_tree.attrs(db, krate, field_id.into()); + let field = &item_tree[field_id]; + if attrs.is_cfg_enabled(cfg_options) { + arena.alloc(lower_field(item_tree, field, override_visibility)); + } else { + diagnostics.push(DefDiagnostic::unconfigured_code( + container, + InFile::new( + current_file_id, + match field.ast_id { + FieldAstId::Record(it) => it.upcast(), + FieldAstId::Tuple(it) => it.upcast(), + }, + ), + attrs.cfg().unwrap(), + cfg_options.clone(), + )) } } - VariantData::Record(arena) + (VariantData::Record(arena), diagnostics) } Fields::Tuple(flds) => { let mut arena = Arena::new(); for field_id in flds.clone() { - if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) { - arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility)); + let attrs = item_tree.attrs(db, krate, field_id.into()); + let field = &item_tree[field_id]; + if attrs.is_cfg_enabled(cfg_options) { + arena.alloc(lower_field(item_tree, field, override_visibility)); + } else { + diagnostics.push(DefDiagnostic::unconfigured_code( + container, + InFile::new( + current_file_id, + match field.ast_id { + FieldAstId::Record(it) => it.upcast(), + FieldAstId::Tuple(it) => it.upcast(), + }, + ), + attrs.cfg().unwrap(), + cfg_options.clone(), + )) } } - VariantData::Tuple(arena) + (VariantData::Tuple(arena), diagnostics) } - Fields::Unit => VariantData::Unit, + Fields::Unit => (VariantData::Unit, diagnostics), } } diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 22f5fb9926..759f3b8c04 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -27,7 +27,7 @@ use crate::{ macro_id_to_def_id, nameres::DefMap, path::{ModPath, Path}, - src::HasSource, + src::{HasChildSource, HasSource}, AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId, UnresolvedMacro, }; @@ -311,7 +311,20 @@ impl Body { DefWithBodyId::FunctionId(f) => { let f = f.lookup(db); let src = f.source(db); - params = src.value.param_list(); + params = src.value.param_list().map(|param_list| { + let item_tree = f.id.item_tree(db); + let func = &item_tree[f.id.value]; + let krate = f.container.module(db).krate; + let crate_graph = db.crate_graph(); + ( + param_list, + func.params.clone().map(move |param| { + item_tree + .attrs(db, krate, param.into()) + .is_cfg_enabled(&crate_graph[krate].cfg_options) + }), + ) + }); (src.file_id, f.module(db), src.value.body().map(ast::Expr::from)) } DefWithBodyId::ConstId(c) => { @@ -324,10 +337,17 @@ impl Body { let src = s.source(db); (src.file_id, s.module(db), src.value.body()) } + DefWithBodyId::VariantId(v) => { + let e = v.parent.lookup(db); + let src = v.parent.child_source(db); + let variant = &src.value[v.local_id]; + (src.file_id, e.container, variant.expr()) + } }; let expander = Expander::new(db, file_id, module); let (mut body, source_map) = Body::new(db, expander, params, body); body.shrink_to_fit(); + (Arc::new(body), Arc::new(source_map)) } @@ -364,7 +384,7 @@ impl Body { fn new( db: &dyn DefDatabase, expander: Expander, - params: Option, + params: Option<(ast::ParamList, impl Iterator)>, body: Option, ) -> (Body, BodySourceMap) { lower::lower(db, expander, params, body) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 3b3297f781..ccc01c3efc 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -29,8 +29,9 @@ use crate::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, db::DefDatabase, expr::{ - dummy_expr_id, Array, BindingAnnotation, Expr, ExprId, FloatTypeWrapper, Label, LabelId, - Literal, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField, Statement, + dummy_expr_id, Array, BindingAnnotation, ClosureKind, Expr, ExprId, FloatTypeWrapper, + Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat, RecordLitField, + Statement, }, intern::Interned, item_scope::BuiltinShadowMode, @@ -76,7 +77,7 @@ impl<'a> LowerCtx<'a> { pub(super) fn lower( db: &dyn DefDatabase, expander: Expander, - params: Option, + params: Option<(ast::ParamList, impl Iterator)>, body: Option, ) -> (Body, BodySourceMap) { ExprCollector { @@ -97,6 +98,7 @@ pub(super) fn lower( name_to_pat_grouping: Default::default(), is_lowering_inside_or_pat: false, is_lowering_assignee_expr: false, + is_lowering_generator: false, } .collect(params, body) } @@ -111,16 +113,19 @@ struct ExprCollector<'a> { name_to_pat_grouping: FxHashMap>, is_lowering_inside_or_pat: bool, is_lowering_assignee_expr: bool, + is_lowering_generator: bool, } impl ExprCollector<'_> { fn collect( mut self, - param_list: Option, + param_list: Option<(ast::ParamList, impl Iterator)>, body: Option, ) -> (Body, BodySourceMap) { - if let Some(param_list) = param_list { - if let Some(self_param) = param_list.self_param() { + if let Some((param_list, mut attr_enabled)) = param_list { + if let Some(self_param) = + param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false)) + { let ptr = AstPtr::new(&self_param); let param_pat = self.alloc_pat( Pat::Bind { @@ -136,7 +141,11 @@ impl ExprCollector<'_> { self.body.params.push(param_pat); } - for pat in param_list.params().filter_map(|param| param.pat()) { + for pat in param_list + .params() + .zip(attr_enabled) + .filter_map(|(param, enabled)| param.pat().filter(|_| enabled)) + { let param_pat = self.collect_pat(pat); self.body.params.push(param_pat); } @@ -358,6 +367,7 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::Return { expr }, syntax_ptr) } ast::Expr::YieldExpr(e) => { + self.is_lowering_generator = true; let expr = e.expr().map(|e| self.collect_expr(e)); self.alloc_expr(Expr::Yield { expr }, syntax_ptr) } @@ -459,13 +469,31 @@ impl ExprCollector<'_> { .ret_type() .and_then(|r| r.ty()) .map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); + + let prev_is_lowering_generator = self.is_lowering_generator; + self.is_lowering_generator = false; + let body = self.collect_expr_opt(e.body()); + + let closure_kind = if self.is_lowering_generator { + let movability = if e.static_token().is_some() { + Movability::Static + } else { + Movability::Movable + }; + ClosureKind::Generator(movability) + } else { + ClosureKind::Closure + }; + self.is_lowering_generator = prev_is_lowering_generator; + self.alloc_expr( Expr::Closure { args: args.into(), arg_types: arg_types.into(), ret_type, body, + closure_kind, }, syntax_ptr, ) diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index f2fed95444..162d173d52 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -2,8 +2,10 @@ use std::fmt::{self, Write}; +use syntax::ast::HasName; + use crate::{ - expr::{Array, BindingAnnotation, Literal, Statement}, + expr::{Array, BindingAnnotation, ClosureKind, Literal, Movability, Statement}, pretty::{print_generic_args, print_path, print_type_ref}, type_ref::TypeRef, }; @@ -32,6 +34,16 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo }; format!("const {} = ", name) } + DefWithBodyId::VariantId(it) => { + needs_semi = false; + let src = it.parent.child_source(db); + let variant = &src.value[it.local_id]; + let name = match &variant.name() { + Some(name) => name.to_string(), + None => "_".to_string(), + }; + format!("{}", name) + } }; let mut p = Printer { body, buf: header, indent_level: 0, needs_indent: false }; @@ -350,7 +362,10 @@ impl<'a> Printer<'a> { self.print_expr(*index); w!(self, "]"); } - Expr::Closure { args, arg_types, ret_type, body } => { + Expr::Closure { args, arg_types, ret_type, body, closure_kind } => { + if let ClosureKind::Generator(Movability::Static) = closure_kind { + w!(self, "static "); + } w!(self, "|"); for (i, (pat, ty)) in args.iter().zip(arg_types.iter()).enumerate() { if i != 0 { diff --git a/crates/hir-def/src/builtin_attr.rs b/crates/hir-def/src/builtin_attr.rs index 0e7ce5f85f..39581b33a8 100644 --- a/crates/hir-def/src/builtin_attr.rs +++ b/crates/hir-def/src/builtin_attr.rs @@ -379,7 +379,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== rustc_attr!(rustc_allocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), - rustc_attr!(rustc_allocator_nounwind, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_nounwind, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), gated!( alloc_error_handler, Normal, template!(Word), WarnFollowing, experimental!(alloc_error_handler) diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs index 5b1435e8f4..bb13165257 100644 --- a/crates/hir-def/src/child_by_source.rs +++ b/crates/hir-def/src/child_by_source.rs @@ -198,6 +198,10 @@ impl ChildBySource for EnumId { impl ChildBySource for DefWithBodyId { fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { let body = db.body(*self); + if let &DefWithBodyId::VariantId(v) = self { + VariantId::EnumVariantId(v).child_by_source_to(db, res, file_id) + } + for (_, def_map) in body.blocks(db) { // All block expressions are merged into the same map, because they logically all add // inner items to the containing `DefWithBodyId`. diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 631ae3cf11..2dc69b00ac 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -219,7 +219,7 @@ impl TraitData { pub(crate) fn trait_data_with_diagnostics_query( db: &dyn DefDatabase, tr: TraitId, - ) -> (Arc, Arc>) { + ) -> (Arc, Arc<[DefDiagnostic]>) { let tr_loc @ ItemLoc { container: module_id, id: tree_id } = tr.lookup(db); let item_tree = tree_id.item_tree(db); let tr_def = &item_tree[tree_id.value]; @@ -251,7 +251,7 @@ impl TraitData { visibility, skip_array_during_method_dispatch, }), - Arc::new(diagnostics), + diagnostics.into(), ) } @@ -299,7 +299,7 @@ impl ImplData { pub(crate) fn impl_data_with_diagnostics_query( db: &dyn DefDatabase, id: ImplId, - ) -> (Arc, Arc>) { + ) -> (Arc, Arc<[DefDiagnostic]>) { let _p = profile::span("impl_data_with_diagnostics_query"); let ItemLoc { container: module_id, id: tree_id } = id.lookup(db); @@ -318,7 +318,7 @@ impl ImplData { ( Arc::new(ImplData { target_trait, self_ty, items, is_negative, attribute_calls }), - Arc::new(diagnostics), + diagnostics.into(), ) } diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 40b2f734b7..431c825549 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -97,24 +97,33 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[salsa::invoke(StructData::struct_data_query)] fn struct_data(&self, id: StructId) -> Arc; + #[salsa::invoke(StructData::struct_data_with_diagnostics_query)] + fn struct_data_with_diagnostics(&self, id: StructId) + -> (Arc, Arc<[DefDiagnostic]>); + #[salsa::invoke(StructData::union_data_query)] fn union_data(&self, id: UnionId) -> Arc; + #[salsa::invoke(StructData::union_data_with_diagnostics_query)] + fn union_data_with_diagnostics(&self, id: UnionId) -> (Arc, Arc<[DefDiagnostic]>); + #[salsa::invoke(EnumData::enum_data_query)] fn enum_data(&self, e: EnumId) -> Arc; + #[salsa::invoke(EnumData::enum_data_with_diagnostics_query)] + fn enum_data_with_diagnostics(&self, e: EnumId) -> (Arc, Arc<[DefDiagnostic]>); + #[salsa::invoke(ImplData::impl_data_query)] fn impl_data(&self, e: ImplId) -> Arc; #[salsa::invoke(ImplData::impl_data_with_diagnostics_query)] - fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc, Arc>); + fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc, Arc<[DefDiagnostic]>); #[salsa::invoke(TraitData::trait_data_query)] fn trait_data(&self, e: TraitId) -> Arc; #[salsa::invoke(TraitData::trait_data_with_diagnostics_query)] - fn trait_data_with_diagnostics(&self, tr: TraitId) - -> (Arc, Arc>); + fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc, Arc<[DefDiagnostic]>); #[salsa::invoke(TypeAliasData::type_alias_data_query)] fn type_alias_data(&self, e: TypeAliasId) -> Arc; diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs index 419d3feec3..1626465502 100644 --- a/crates/hir-def/src/expr.rs +++ b/crates/hir-def/src/expr.rs @@ -198,6 +198,7 @@ pub enum Expr { arg_types: Box<[Option>]>, ret_type: Option>, body: ExprId, + closure_kind: ClosureKind, }, Tuple { exprs: Box<[ExprId]>, @@ -211,6 +212,18 @@ pub enum Expr { Underscore, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ClosureKind { + Closure, + Generator(Movability), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Movability { + Static, + Movable, +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Array { ElementList { elements: Box<[ExprId]>, is_assignee_expr: bool }, diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index b94b500040..c70e6fdccd 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -333,8 +333,8 @@ fn calculate_best_path( db, def_map, visited_modules, - from, crate_root, + from, info.container, max_len - 1, prefixed, diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 3342d4db4a..570344596d 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -943,6 +943,7 @@ impl AssocItem { pub struct Variant { pub name: Name, pub fields: Fields, + pub ast_id: FileAstId, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -952,10 +953,17 @@ pub enum Fields { Unit, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FieldAstId { + Record(FileAstId), + Tuple(FileAstId), +} + /// A single field of an enum variant or struct #[derive(Debug, Clone, PartialEq, Eq)] pub struct Field { pub name: Name, pub type_ref: Interned, pub visibility: RawVisibilityId, + pub ast_id: FieldAstId, } diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index 7f2551e941..077a1b619d 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -184,7 +184,8 @@ impl<'a> Ctx<'a> { let name = field.name()?.as_name(); let visibility = self.lower_visibility(field); let type_ref = self.lower_type_ref_opt(field.ty()); - let res = Field { name, type_ref, visibility }; + let ast_id = FieldAstId::Record(self.source_ast_id_map.ast_id(field)); + let res = Field { name, type_ref, visibility, ast_id }; Some(res) } @@ -203,7 +204,8 @@ impl<'a> Ctx<'a> { let name = Name::new_tuple_field(idx); let visibility = self.lower_visibility(field); let type_ref = self.lower_type_ref_opt(field.ty()); - Field { name, type_ref, visibility } + let ast_id = FieldAstId::Tuple(self.source_ast_id_map.ast_id(field)); + Field { name, type_ref, visibility, ast_id } } fn lower_union(&mut self, union: &ast::Union) -> Option> { @@ -247,7 +249,8 @@ impl<'a> Ctx<'a> { fn lower_variant(&mut self, variant: &ast::Variant) -> Option { let name = variant.name()?.as_name(); let fields = self.lower_fields(&variant.kind()); - let res = Variant { name, fields }; + let ast_id = self.source_ast_id_map.ast_id(variant); + let res = Variant { name, fields, ast_id }; Some(res) } diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 34dd817fd1..da1643152c 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -115,7 +115,7 @@ impl<'a> Printer<'a> { w!(self, "{{"); self.indented(|this| { for field in fields.clone() { - let Field { visibility, name, type_ref } = &this.tree[field]; + let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field); this.print_visibility(*visibility); w!(this, "{}: ", name); @@ -129,7 +129,7 @@ impl<'a> Printer<'a> { w!(self, "("); self.indented(|this| { for field in fields.clone() { - let Field { visibility, name, type_ref } = &this.tree[field]; + let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field); this.print_visibility(*visibility); w!(this, "{}: ", name); @@ -323,7 +323,7 @@ impl<'a> Printer<'a> { self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { for variant in variants.clone() { - let Variant { name, fields } = &this.tree[variant]; + let Variant { name, fields, ast_id: _ } = &this.tree[variant]; this.print_attrs_of(variant); w!(this, "{}", name); this.print_fields(fields); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 32ebfda4fd..5c7aa72349 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -474,16 +474,24 @@ pub enum DefWithBodyId { FunctionId(FunctionId), StaticId(StaticId), ConstId(ConstId), + VariantId(EnumVariantId), } impl_from!(FunctionId, ConstId, StaticId for DefWithBodyId); +impl From for DefWithBodyId { + fn from(id: EnumVariantId) -> Self { + DefWithBodyId::VariantId(id) + } +} + impl DefWithBodyId { pub fn as_generic_def_id(self) -> Option { match self { DefWithBodyId::FunctionId(f) => Some(f.into()), DefWithBodyId::StaticId(_) => None, DefWithBodyId::ConstId(c) => Some(c.into()), + DefWithBodyId::VariantId(c) => Some(c.into()), } } } @@ -681,6 +689,7 @@ impl HasModule for DefWithBodyId { DefWithBodyId::FunctionId(it) => it.lookup(db).module(db), DefWithBodyId::StaticId(it) => it.lookup(db).module(db), DefWithBodyId::ConstId(it) => it.lookup(db).module(db), + DefWithBodyId::VariantId(it) => it.parent.lookup(db).container, } } } @@ -691,6 +700,7 @@ impl DefWithBodyId { DefWithBodyId::FunctionId(it) => it.lookup(db).id.value.into(), DefWithBodyId::StaticId(it) => it.lookup(db).id.value.into(), DefWithBodyId::ConstId(it) => it.lookup(db).id.value.into(), + DefWithBodyId::VariantId(it) => it.parent.lookup(db).id.value.into(), } } } diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index 4f626105a5..c04cd16519 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -93,12 +93,12 @@ macro_rules! option_env {() => {}} fn main() { option_env!("TEST_ENV_VAR"); } "#, - expect![[r##" + expect![[r#" #[rustc_builtin_macro] macro_rules! option_env {() => {}} -fn main() { std::option::Option::None:: < &str>; } -"##]], +fn main() { $crate::option::Option::None:: < &str>; } +"#]], ); } @@ -191,7 +191,7 @@ fn main() { format_args!("{} {:?}", arg1(a, b, c), arg2); } "#, - expect![[r##" + expect![[r#" #[rustc_builtin_macro] macro_rules! format_args { ($fmt:expr) => ({ /* compiler built-in */ }); @@ -199,9 +199,9 @@ macro_rules! format_args { } fn main() { - std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(arg1(a, b, c)), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(arg2), std::fmt::Display::fmt), ]); + $crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(arg1(a, b, c)), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(arg2), $crate::fmt::Display::fmt), ]); } -"##]], +"#]], ); } @@ -219,7 +219,7 @@ fn main() { format_args!("{} {:?}", a::(), b); } "#, - expect![[r##" + expect![[r#" #[rustc_builtin_macro] macro_rules! format_args { ($fmt:expr) => ({ /* compiler built-in */ }); @@ -227,9 +227,9 @@ macro_rules! format_args { } fn main() { - std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::()), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(b), std::fmt::Display::fmt), ]); + $crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(a::()), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(b), $crate::fmt::Display::fmt), ]); } -"##]], +"#]], ); } @@ -248,7 +248,7 @@ fn main() { format_args!/*+errors*/("{} {:?}", a.); } "#, - expect![[r##" + expect![[r#" #[rustc_builtin_macro] macro_rules! format_args { ($fmt:expr) => ({ /* compiler built-in */ }); @@ -258,9 +258,9 @@ macro_rules! format_args { fn main() { let _ = /* parse error: expected field name or number */ -std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.), std::fmt::Display::fmt), ]); +$crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(a.), $crate::fmt::Display::fmt), ]); } -"##]], +"#]], ); } diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index 9242b48c59..9ffc218818 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -2122,7 +2122,7 @@ impl ModCollector<'_, '_> { fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) { let ast_id = item.ast_id(self.item_tree); - let ast_id = InFile::new(self.file_id(), ast_id); + let ast_id = InFile::new(self.file_id(), ast_id.upcast()); self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code( self.module_id, ast_id, diff --git a/crates/hir-def/src/nameres/diagnostics.rs b/crates/hir-def/src/nameres/diagnostics.rs index ed7e920fd2..0661422919 100644 --- a/crates/hir-def/src/nameres/diagnostics.rs +++ b/crates/hir-def/src/nameres/diagnostics.rs @@ -4,7 +4,7 @@ use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use hir_expand::MacroCallKind; use la_arena::Idx; -use syntax::ast; +use syntax::ast::{self, AnyHasAttrs}; use crate::{ attr::AttrId, @@ -22,7 +22,7 @@ pub enum DefDiagnosticKind { UnresolvedImport { id: ItemTreeId, index: Idx }, - UnconfiguredCode { ast: AstId, cfg: CfgExpr, opts: CfgOptions }, + UnconfiguredCode { ast: AstId, cfg: CfgExpr, opts: CfgOptions }, UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId }, @@ -75,7 +75,7 @@ impl DefDiagnostic { pub fn unconfigured_code( container: LocalModuleId, - ast: AstId, + ast: AstId, cfg: CfgExpr, opts: CfgOptions, ) -> Self { diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 8aa5973cac..070f683713 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -839,6 +839,7 @@ impl HasResolver for DefWithBodyId { DefWithBodyId::ConstId(c) => c.resolver(db), DefWithBodyId::FunctionId(f) => f.resolver(db), DefWithBodyId::StaticId(s) => s.resolver(db), + DefWithBodyId::VariantId(v) => v.parent.resolver(db), } } } diff --git a/crates/hir-expand/Cargo.toml b/crates/hir-expand/Cargo.toml index dfd470ffca..3359c99b39 100644 --- a/crates/hir-expand/Cargo.toml +++ b/crates/hir-expand/Cargo.toml @@ -15,11 +15,11 @@ tracing = "0.1.35" either = "1.7.0" rustc-hash = "1.1.0" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } -itertools = "0.10.3" +itertools = "0.10.5" hashbrown = { version = "0.12.1", features = [ "inline-more", ], default-features = false } -smallvec = { version = "1.9.0", features = ["const_new"] } +smallvec = { version = "1.10.0", features = ["const_new"] } stdx = { path = "../stdx", version = "0.0.0" } base-db = { path = "../base-db", version = "0.0.0" } diff --git a/crates/hir-expand/src/ast_id_map.rs b/crates/hir-expand/src/ast_id_map.rs index 11c0a6764e..2b27db0e95 100644 --- a/crates/hir-expand/src/ast_id_map.rs +++ b/crates/hir-expand/src/ast_id_map.rs @@ -93,7 +93,12 @@ impl AstIdMap { // trait does not change ids of top-level items, which helps caching. bdfs(node, |it| { let kind = it.kind(); - if ast::Item::can_cast(kind) || ast::BlockExpr::can_cast(kind) { + if ast::Item::can_cast(kind) + || ast::BlockExpr::can_cast(kind) + || ast::Variant::can_cast(kind) + || ast::RecordField::can_cast(kind) + || ast::TupleField::can_cast(kind) + { res.alloc(&it); true } else { diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs index 8befa7f7da..7b19518e25 100644 --- a/crates/hir-expand/src/builtin_fn_macro.rs +++ b/crates/hir-expand/src/builtin_fn_macro.rs @@ -238,9 +238,9 @@ fn format_args_expand( ) -> ExpandResult { // We expand `format_args!("", a1, a2)` to // ``` - // std::fmt::Arguments::new_v1(&[], &[ - // std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt), - // std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt), + // $crate::fmt::Arguments::new_v1(&[], &[ + // $crate::fmt::ArgumentV1::new(&arg1,$crate::fmt::Display::fmt), + // $crate::fmt::ArgumentV1::new(&arg2,$crate::fmt::Display::fmt), // ]) // ```, // which is still not really correct, but close enough for now @@ -262,10 +262,10 @@ fn format_args_expand( } let _format_string = args.remove(0); let arg_tts = args.into_iter().flat_map(|arg| { - quote! { std::fmt::ArgumentV1::new(&(#arg), std::fmt::Display::fmt), } + quote! { #DOLLAR_CRATE::fmt::ArgumentV1::new(&(#arg), #DOLLAR_CRATE::fmt::Display::fmt), } }.token_trees); let expanded = quote! { - std::fmt::Arguments::new_v1(&[], &[##arg_tts]) + #DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts]) }; ExpandResult::ok(expanded) } @@ -675,8 +675,8 @@ fn option_env_expand( }; let expanded = match get_env_inner(db, arg_id, &key) { - None => quote! { std::option::Option::None::<&str> }, - Some(s) => quote! { std::option::Some(#s) }, + None => quote! { #DOLLAR_CRATE::option::Option::None::<&str> }, + Some(s) => quote! { #DOLLAR_CRATE::option::Some(#s) }, }; ExpandResult::ok(ExpandedEager::new(expanded)) diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index bc97ee15c7..87e4db0398 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -221,8 +221,16 @@ pub fn expand_speculative( fixup::reverse_fixups(&mut speculative_expansion.value, &spec_args_tmap, &fixups.undo_info); let (node, rev_tmap) = token_tree_to_syntax_node(&speculative_expansion.value, expand_to); - let range = rev_tmap.first_range_by_token(token_id, token_to_map.kind())?; - let token = node.syntax_node().covering_element(range).into_token()?; + let syntax_node = node.syntax_node(); + let token = rev_tmap + .ranges_by_token(token_id, token_to_map.kind()) + .filter_map(|range| syntax_node.covering_element(range).into_token()) + .min_by_key(|t| { + // prefer tokens of the same kind and text + // Note the inversion of the score here, as we want to prefer the first token in case + // of all tokens having the same score + (t.kind() != token_to_map.kind()) as u8 + (t.text() != token_to_map.text()) as u8 + })?; Some((node.syntax_node(), token)) } diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index fc128102f2..a5b499fe8d 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -811,6 +811,31 @@ impl<'a> InFile<&'a SyntaxNode> { _ => None, } } + + pub fn original_syntax_node(self, db: &dyn db::AstDatabase) -> Option> { + // This kind of upmapping can only be achieved in attribute expanded files, + // as we don't have node inputs otherwise and therefor can't find an `N` node in the input + if !self.file_id.is_macro() { + return Some(self.map(Clone::clone)); + } else if !self.file_id.is_attr_macro(db) { + return None; + } + + if let Some(InFile { file_id, value: (first, last) }) = ascend_node_border_tokens(db, self) + { + if file_id.is_macro() { + let range = first.text_range().cover(last.text_range()); + tracing::error!("Failed mapping out of macro file for {:?}", range); + return None; + } + // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes + let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?; + let kind = self.value.kind(); + let value = anc.ancestors().find(|it| it.kind() == kind)?; + return Some(InFile::new(file_id, value)); + } + None + } } impl InFile { diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index d7586d129b..68413df420 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -259,6 +259,7 @@ macro_rules! __known_path { (core::future::Future) => {}; (core::future::IntoFuture) => {}; (core::ops::Try) => {}; + (core::ops::FromResidual) => {}; ($path:path) => { compile_error!("Please register your known path in the path module") }; diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 4ce21a5796..8a735b965a 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -263,6 +263,7 @@ pub mod known { Iterator, IntoIterator, Item, + IntoIter, Try, Ok, Future, @@ -278,6 +279,8 @@ pub mod known { RangeToInclusive, RangeTo, Range, + Residual, + FromResidual, Neg, Not, None, diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index 7f143f396c..ed13275bab 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -11,18 +11,19 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" -itertools = "0.10.3" +itertools = "0.10.5" arrayvec = "0.7.2" -smallvec = "1.9.0" +smallvec = "1.10.0" ena = "0.14.0" tracing = "0.1.35" rustc-hash = "1.1.0" scoped-tls = "1.0.0" -chalk-solve = { version = "0.84.0", default-features = false } -chalk-ir = "0.84.0" -chalk-recursive = { version = "0.84.0", default-features = false } +chalk-solve = { version = "0.86.0", default-features = false } +chalk-ir = "0.86.0" +chalk-recursive = { version = "0.86.0", default-features = false } +chalk-derive = "0.86.0" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } -once_cell = "1.12.0" +once_cell = "1.15.0" typed-arena = "2.0.1" stdx = { path = "../stdx", version = "0.0.0" } @@ -37,7 +38,7 @@ limit = { path = "../limit", version = "0.0.0" } test-utils = { path = "../test-utils" } expect-test = "1.4.0" tracing = "0.1.35" -tracing-subscriber = { version = "0.3.14", default-features = false, features = [ +tracing-subscriber = { version = "0.3.16", default-features = false, features = [ "env-filter", "registry", ] } diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs index 344036dd81..78911d8dc0 100644 --- a/crates/hir-ty/src/autoderef.rs +++ b/crates/hir-ty/src/autoderef.rs @@ -1,7 +1,7 @@ //! In certain situations, rust automatically inserts derefs as necessary: for //! example, field accesses `foo.bar` still work when `foo` is actually a //! reference to a type with the field `bar`. This is an approximation of the -//! logic in rustc (which lives in librustc_typeck/check/autoderef.rs). +//! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs). use std::sync::Arc; @@ -123,13 +123,14 @@ fn deref_by_trait(table: &mut InferenceTable<'_>, ty: Ty) -> Option { let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?; let projection = { - let b = TyBuilder::assoc_type_projection(db, target); + let b = TyBuilder::subst_for_def(db, deref_trait, None); if b.remaining() != 1 { // the Target type + Deref trait should only have one generic parameter, // namely Deref's Self type return None; } - b.push(ty).build() + let deref_subst = b.push(ty).build(); + TyBuilder::assoc_type_projection(db, target, Some(deref_subst)).build() }; // Check that the type implements Deref at all diff --git a/crates/hir-ty/src/builder.rs b/crates/hir-ty/src/builder.rs index 94d7806cb6..9ae752556d 100644 --- a/crates/hir-ty/src/builder.rs +++ b/crates/hir-ty/src/builder.rs @@ -6,19 +6,19 @@ use chalk_ir::{ cast::{Cast, CastTo, Caster}, fold::TypeFoldable, interner::HasInterner, - AdtId, BoundVar, DebruijnIndex, Scalar, + AdtId, DebruijnIndex, Scalar, }; use hir_def::{ - builtin_type::BuiltinType, generics::TypeOrConstParamData, ConstParamId, GenericDefId, TraitId, - TypeAliasId, + builtin_type::BuiltinType, generics::TypeOrConstParamData, ConstParamId, DefWithBodyId, + GenericDefId, TraitId, TypeAliasId, }; use smallvec::SmallVec; use crate::{ consteval::unknown_const_as_generic, db::HirDatabase, infer::unify::InferenceTable, primitive, - to_assoc_type_id, to_chalk_trait_id, utils::generics, Binders, CallableSig, ConstData, - ConstValue, GenericArg, GenericArgData, Interner, ProjectionTy, Substitution, TraitRef, Ty, - TyDefId, TyExt, TyKind, ValueTyDefId, + to_assoc_type_id, to_chalk_trait_id, utils::generics, Binders, BoundVar, CallableSig, + GenericArg, Interner, ProjectionTy, Substitution, TraitRef, Ty, TyDefId, TyExt, TyKind, + ValueTyDefId, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -34,17 +34,32 @@ pub struct TyBuilder { data: D, vec: SmallVec<[GenericArg; 2]>, param_kinds: SmallVec<[ParamKind; 2]>, + parent_subst: Substitution, } impl TyBuilder { fn with_data(self, data: B) -> TyBuilder { - TyBuilder { data, param_kinds: self.param_kinds, vec: self.vec } + TyBuilder { + data, + vec: self.vec, + param_kinds: self.param_kinds, + parent_subst: self.parent_subst, + } } } impl TyBuilder { - fn new(data: D, param_kinds: SmallVec<[ParamKind; 2]>) -> TyBuilder { - TyBuilder { data, vec: SmallVec::with_capacity(param_kinds.len()), param_kinds } + fn new( + data: D, + param_kinds: SmallVec<[ParamKind; 2]>, + parent_subst: Option, + ) -> Self { + let parent_subst = parent_subst.unwrap_or_else(|| Substitution::empty(Interner)); + Self { data, vec: SmallVec::with_capacity(param_kinds.len()), param_kinds, parent_subst } + } + + fn new_empty(data: D) -> Self { + TyBuilder::new(data, SmallVec::new(), None) } fn build_internal(self) -> (D, Substitution) { @@ -52,13 +67,18 @@ impl TyBuilder { for (a, e) in self.vec.iter().zip(self.param_kinds.iter()) { self.assert_match_kind(a, e); } - let subst = Substitution::from_iter(Interner, self.vec); + let subst = Substitution::from_iter( + Interner, + self.vec.into_iter().chain(self.parent_subst.iter(Interner).cloned()), + ); (self.data, subst) } pub fn push(mut self, arg: impl CastTo) -> Self { + assert!(self.remaining() > 0); let arg = arg.cast(Interner); let expected_kind = &self.param_kinds[self.vec.len()]; + let arg_kind = match arg.data(Interner) { chalk_ir::GenericArgData::Ty(_) => ParamKind::Type, chalk_ir::GenericArgData::Lifetime(_) => panic!("Got lifetime in TyBuilder::push"), @@ -68,7 +88,9 @@ impl TyBuilder { } }; assert_eq!(*expected_kind, arg_kind); + self.vec.push(arg); + self } @@ -79,20 +101,12 @@ impl TyBuilder { pub fn fill_with_bound_vars(self, debruijn: DebruijnIndex, starting_from: usize) -> Self { // self.fill is inlined to make borrow checker happy let mut this = self; - let other = this.param_kinds.iter().skip(this.vec.len()); + let other = &this.param_kinds[this.vec.len()..]; let filler = (starting_from..).zip(other).map(|(idx, kind)| match kind { - ParamKind::Type => { - GenericArgData::Ty(TyKind::BoundVar(BoundVar::new(debruijn, idx)).intern(Interner)) - .intern(Interner) + ParamKind::Type => BoundVar::new(debruijn, idx).to_ty(Interner).cast(Interner), + ParamKind::Const(ty) => { + BoundVar::new(debruijn, idx).to_const(Interner, ty.clone()).cast(Interner) } - ParamKind::Const(ty) => GenericArgData::Const( - ConstData { - value: ConstValue::BoundVar(BoundVar::new(debruijn, idx)), - ty: ty.clone(), - } - .intern(Interner), - ) - .intern(Interner), }); this.vec.extend(filler.take(this.remaining()).casted(Interner)); assert_eq!(this.remaining(), 0); @@ -102,8 +116,8 @@ impl TyBuilder { pub fn fill_with_unknown(self) -> Self { // self.fill is inlined to make borrow checker happy let mut this = self; - let filler = this.param_kinds.iter().skip(this.vec.len()).map(|x| match x { - ParamKind::Type => GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner), + let filler = this.param_kinds[this.vec.len()..].iter().map(|x| match x { + ParamKind::Type => TyKind::Error.intern(Interner).cast(Interner), ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), }); this.vec.extend(filler.casted(Interner)); @@ -113,33 +127,17 @@ impl TyBuilder { pub(crate) fn fill_with_inference_vars(self, table: &mut InferenceTable<'_>) -> Self { self.fill(|x| match x { - ParamKind::Type => GenericArgData::Ty(table.new_type_var()).intern(Interner), - ParamKind::Const(ty) => { - GenericArgData::Const(table.new_const_var(ty.clone())).intern(Interner) - } + ParamKind::Type => table.new_type_var().cast(Interner), + ParamKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner), }) } pub fn fill(mut self, filler: impl FnMut(&ParamKind) -> GenericArg) -> Self { - self.vec.extend(self.param_kinds.iter().skip(self.vec.len()).map(filler)); + self.vec.extend(self.param_kinds[self.vec.len()..].iter().map(filler)); assert_eq!(self.remaining(), 0); self } - pub fn use_parent_substs(mut self, parent_substs: &Substitution) -> Self { - assert!(self.vec.is_empty()); - assert!(parent_substs.len(Interner) <= self.param_kinds.len()); - self.extend(parent_substs.iter(Interner).cloned()); - self - } - - fn extend(&mut self, it: impl Iterator + Clone) { - for x in it.clone().zip(self.param_kinds.iter().skip(self.vec.len())) { - self.assert_match_kind(&x.0, &x.1); - } - self.vec.extend(it); - } - fn assert_match_kind(&self, a: &chalk_ir::GenericArg, e: &ParamKind) { match (a.data(Interner), e) { (chalk_ir::GenericArgData::Ty(_), ParamKind::Type) @@ -188,21 +186,42 @@ impl TyBuilder<()> { params.placeholder_subst(db) } - pub fn subst_for_def(db: &dyn HirDatabase, def: impl Into) -> TyBuilder<()> { - let def = def.into(); - let params = generics(db.upcast(), def); - TyBuilder::new( - (), - params - .iter() - .map(|(id, data)| match data { - TypeOrConstParamData::TypeParamData(_) => ParamKind::Type, - TypeOrConstParamData::ConstParamData(_) => { - ParamKind::Const(db.const_param_ty(ConstParamId::from_unchecked(id))) - } - }) - .collect(), - ) + pub fn subst_for_def( + db: &dyn HirDatabase, + def: impl Into, + parent_subst: Option, + ) -> TyBuilder<()> { + let generics = generics(db.upcast(), def.into()); + assert!(generics.parent_generics().is_some() == parent_subst.is_some()); + let params = generics + .iter_self() + .map(|(id, data)| match data { + TypeOrConstParamData::TypeParamData(_) => ParamKind::Type, + TypeOrConstParamData::ConstParamData(_) => { + ParamKind::Const(db.const_param_ty(ConstParamId::from_unchecked(id))) + } + }) + .collect(); + TyBuilder::new((), params, parent_subst) + } + + /// Creates a `TyBuilder` to build `Substitution` for a generator defined in `parent`. + /// + /// A generator's substitution consists of: + /// - resume type of generator + /// - yield type of generator ([`Generator::Yield`](std::ops::Generator::Yield)) + /// - return type of generator ([`Generator::Return`](std::ops::Generator::Return)) + /// - generic parameters in scope on `parent` + /// in this order. + /// + /// This method prepopulates the builder with placeholder substitution of `parent`, so you + /// should only push exactly 3 `GenericArg`s before building. + pub fn subst_for_generator(db: &dyn HirDatabase, parent: DefWithBodyId) -> TyBuilder<()> { + let parent_subst = + parent.as_generic_def_id().map(|p| generics(db.upcast(), p).placeholder_subst(db)); + // These represent resume type, yield type, and return type of generator. + let params = std::iter::repeat(ParamKind::Type).take(3).collect(); + TyBuilder::new((), params, parent_subst) } pub fn build(self) -> Substitution { @@ -213,7 +232,7 @@ impl TyBuilder<()> { impl TyBuilder { pub fn adt(db: &dyn HirDatabase, def: hir_def::AdtId) -> TyBuilder { - TyBuilder::subst_for_def(db, def).with_data(def) + TyBuilder::subst_for_def(db, def, None).with_data(def) } pub fn fill_with_defaults( @@ -221,16 +240,27 @@ impl TyBuilder { db: &dyn HirDatabase, mut fallback: impl FnMut() -> Ty, ) -> Self { + // Note that we're building ADT, so we never have parent generic parameters. let defaults = db.generic_defaults(self.data.into()); + let dummy_ty = TyKind::Error.intern(Interner).cast(Interner); for default_ty in defaults.iter().skip(self.vec.len()) { - if let GenericArgData::Ty(x) = default_ty.skip_binders().data(Interner) { + // NOTE(skip_binders): we only check if the arg type is error type. + if let Some(x) = default_ty.skip_binders().ty(Interner) { if x.is_unknown() { self.vec.push(fallback().cast(Interner)); continue; } - }; - // each default can depend on the previous parameters - let subst_so_far = Substitution::from_iter(Interner, self.vec.clone()); + } + // Each default can only depend on the previous parameters. + // FIXME: we don't handle const generics here. + let subst_so_far = Substitution::from_iter( + Interner, + self.vec + .iter() + .cloned() + .chain(iter::repeat(dummy_ty.clone())) + .take(self.param_kinds.len()), + ); self.vec.push(default_ty.clone().substitute(Interner, &subst_so_far).cast(Interner)); } self @@ -245,7 +275,7 @@ impl TyBuilder { pub struct Tuple(usize); impl TyBuilder { pub fn tuple(size: usize) -> TyBuilder { - TyBuilder::new(Tuple(size), iter::repeat(ParamKind::Type).take(size).collect()) + TyBuilder::new(Tuple(size), iter::repeat(ParamKind::Type).take(size).collect(), None) } pub fn build(self) -> Ty { @@ -256,7 +286,7 @@ impl TyBuilder { impl TyBuilder { pub fn trait_ref(db: &dyn HirDatabase, def: TraitId) -> TyBuilder { - TyBuilder::subst_for_def(db, def).with_data(def) + TyBuilder::subst_for_def(db, def, None).with_data(def) } pub fn build(self) -> TraitRef { @@ -266,8 +296,12 @@ impl TyBuilder { } impl TyBuilder { - pub fn assoc_type_projection(db: &dyn HirDatabase, def: TypeAliasId) -> TyBuilder { - TyBuilder::subst_for_def(db, def).with_data(def) + pub fn assoc_type_projection( + db: &dyn HirDatabase, + def: TypeAliasId, + parent_subst: Option, + ) -> TyBuilder { + TyBuilder::subst_for_def(db, def, parent_subst).with_data(def) } pub fn build(self) -> ProjectionTy { @@ -277,19 +311,6 @@ impl TyBuilder { } impl + TypeFoldable> TyBuilder> { - fn subst_binders(b: Binders) -> Self { - let param_kinds = b - .binders - .iter(Interner) - .map(|x| match x { - chalk_ir::VariableKind::Ty(_) => ParamKind::Type, - chalk_ir::VariableKind::Lifetime => panic!("Got lifetime parameter"), - chalk_ir::VariableKind::Const(ty) => ParamKind::Const(ty.clone()), - }) - .collect(); - TyBuilder::new(b, param_kinds) - } - pub fn build(self) -> T { let (b, subst) = self.build_internal(); b.substitute(Interner, &subst) @@ -297,15 +318,41 @@ impl + TypeFoldable> TyBuilder> { - pub fn def_ty(db: &dyn HirDatabase, def: TyDefId) -> TyBuilder> { - TyBuilder::subst_binders(db.ty(def)) + pub fn def_ty( + db: &dyn HirDatabase, + def: TyDefId, + parent_subst: Option, + ) -> TyBuilder> { + let poly_ty = db.ty(def); + let id: GenericDefId = match def { + TyDefId::BuiltinType(_) => { + assert!(parent_subst.is_none()); + return TyBuilder::new_empty(poly_ty); + } + TyDefId::AdtId(id) => id.into(), + TyDefId::TypeAliasId(id) => id.into(), + }; + TyBuilder::subst_for_def(db, id, parent_subst).with_data(poly_ty) } pub fn impl_self_ty(db: &dyn HirDatabase, def: hir_def::ImplId) -> TyBuilder> { - TyBuilder::subst_binders(db.impl_self_ty(def)) + TyBuilder::subst_for_def(db, def, None).with_data(db.impl_self_ty(def)) } - pub fn value_ty(db: &dyn HirDatabase, def: ValueTyDefId) -> TyBuilder> { - TyBuilder::subst_binders(db.value_ty(def)) + pub fn value_ty( + db: &dyn HirDatabase, + def: ValueTyDefId, + parent_subst: Option, + ) -> TyBuilder> { + let poly_value_ty = db.value_ty(def); + let id = match def.to_generic_def_id() { + Some(id) => id, + None => { + // static items + assert!(parent_subst.is_none()); + return TyBuilder::new_empty(poly_value_ty); + } + }; + TyBuilder::subst_for_def(db, id, parent_subst).with_data(poly_value_ty) } } diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index faec99c7d3..43c3451cab 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -11,6 +11,7 @@ use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait}; use base_db::CrateId; use hir_def::{ + expr::Movability, lang_item::{lang_attr, LangItemTarget}, AssocItemId, GenericDefId, HasModule, ItemContainerId, Lookup, ModuleId, TypeAliasId, }; @@ -26,9 +27,9 @@ use crate::{ to_assoc_type_id, to_chalk_trait_id, traits::ChalkContext, utils::generics, - AliasEq, AliasTy, BoundVar, CallableDefId, DebruijnIndex, FnDefId, Interner, ProjectionTy, - ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, - TyExt, TyKind, WhereClause, + wrap_empty_binders, AliasEq, AliasTy, BoundVar, CallableDefId, DebruijnIndex, FnDefId, + Interner, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, + TraitRefExt, Ty, TyBuilder, TyExt, TyKind, WhereClause, }; pub(crate) type AssociatedTyDatum = chalk_solve::rust_ir::AssociatedTyDatum; @@ -372,17 +373,62 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { } fn generator_datum( &self, - _: chalk_ir::GeneratorId, + id: chalk_ir::GeneratorId, ) -> std::sync::Arc> { - // FIXME - unimplemented!() + let (parent, expr) = self.db.lookup_intern_generator(id.into()); + + // We fill substitution with unknown type, because we only need to know whether the generic + // params are types or consts to build `Binders` and those being filled up are for + // `resume_type`, `yield_type`, and `return_type` of the generator in question. + let subst = TyBuilder::subst_for_generator(self.db, parent).fill_with_unknown().build(); + + let input_output = rust_ir::GeneratorInputOutputDatum { + resume_type: TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)) + .intern(Interner), + yield_type: TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 1)) + .intern(Interner), + return_type: TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 2)) + .intern(Interner), + // FIXME: calculate upvars + upvars: vec![], + }; + + let it = subst + .iter(Interner) + .map(|it| it.constant(Interner).map(|c| c.data(Interner).ty.clone())); + let input_output = crate::make_type_and_const_binders(it, input_output); + + let movability = match self.db.body(parent)[expr] { + hir_def::expr::Expr::Closure { + closure_kind: hir_def::expr::ClosureKind::Generator(movability), + .. + } => movability, + _ => unreachable!("non generator expression interned as generator"), + }; + let movability = match movability { + Movability::Static => rust_ir::Movability::Static, + Movability::Movable => rust_ir::Movability::Movable, + }; + + Arc::new(rust_ir::GeneratorDatum { movability, input_output }) } fn generator_witness_datum( &self, - _: chalk_ir::GeneratorId, + id: chalk_ir::GeneratorId, ) -> std::sync::Arc> { - // FIXME - unimplemented!() + // FIXME: calculate inner types + let inner_types = + rust_ir::GeneratorWitnessExistential { types: wrap_empty_binders(vec![]) }; + + let (parent, _) = self.db.lookup_intern_generator(id.into()); + // See the comment in `generator_datum()` for unknown types. + let subst = TyBuilder::subst_for_generator(self.db, parent).fill_with_unknown().build(); + let it = subst + .iter(Interner) + .map(|it| it.constant(Interner).map(|c| c.data(Interner).ty.clone())); + let inner_types = crate::make_type_and_const_binders(it, inner_types); + + Arc::new(rust_ir::GeneratorWitnessDatum { inner_types }) } fn unification_database(&self) -> &dyn chalk_ir::UnificationDatabase { @@ -429,10 +475,15 @@ pub(crate) fn associated_ty_data_query( let resolver = hir_def::resolver::HasResolver::resolver(type_alias, db.upcast()); let ctx = crate::TyLoweringContext::new(db, &resolver) .with_type_param_mode(crate::lower::ParamLoweringMode::Variable); - let pro_ty = TyBuilder::assoc_type_projection(db, type_alias) + + let trait_subst = TyBuilder::subst_for_def(db, trait_, None) + .fill_with_bound_vars(crate::DebruijnIndex::INNERMOST, generic_params.len_self()) + .build(); + let pro_ty = TyBuilder::assoc_type_projection(db, type_alias, Some(trait_subst)) .fill_with_bound_vars(crate::DebruijnIndex::INNERMOST, 0) .build(); let self_ty = TyKind::Alias(AliasTy::Projection(pro_ty)).intern(Interner); + let mut bounds: Vec<_> = type_alias_data .bounds .iter() @@ -772,10 +823,10 @@ pub(super) fn generic_predicate_to_inline_bound( Some(chalk_ir::Binders::new(binders, rust_ir::InlineBound::TraitBound(trait_bound))) } WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => { - if projection_ty.self_type_parameter(Interner) != self_ty_shifted_in { + let trait_ = projection_ty.trait_(db); + if projection_ty.self_type_parameter(db) != self_ty_shifted_in { return None; } - let trait_ = projection_ty.trait_(db); let args_no_self = projection_ty.substitution.as_slice(Interner)[1..] .iter() .map(|ty| ty.clone().cast(Interner)) diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs index 4a5533c648..e2099d7e50 100644 --- a/crates/hir-ty/src/chalk_ext.rs +++ b/crates/hir-ty/src/chalk_ext.rs @@ -152,7 +152,7 @@ impl TyExt for Ty { TyKind::FnDef(def, parameters) => { let callable_def = db.lookup_intern_callable_def((*def).into()); let sig = db.callable_item_signature(callable_def); - Some(sig.substitute(Interner, ¶meters)) + Some(sig.substitute(Interner, parameters)) } TyKind::Closure(.., substs) => { let sig_param = substs.at(Interner, 0).assert_ty_ref(Interner); @@ -166,6 +166,8 @@ impl TyExt for Ty { let trait_ref = match self.kind(Interner) { // The principal trait bound should be the first element of the bounds. This is an // invariant ensured by `TyLoweringContext::lower_dyn_trait()`. + // FIXME: dyn types may not have principal trait and we don't want to return auto trait + // here. TyKind::Dyn(dyn_ty) => dyn_ty.bounds.skip_binders().interned().get(0).and_then(|b| { match b.skip_binders() { WhereClause::Implemented(trait_ref) => Some(trait_ref), @@ -260,7 +262,7 @@ impl TyExt for Ty { WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(proj), ty: _, - }) => &proj.self_type_parameter(Interner) == self, + }) => &proj.self_type_parameter(db) == self, _ => false, }) .collect::>(); @@ -331,6 +333,7 @@ impl TyExt for Ty { pub trait ProjectionTyExt { fn trait_ref(&self, db: &dyn HirDatabase) -> TraitRef; fn trait_(&self, db: &dyn HirDatabase) -> TraitId; + fn self_type_parameter(&self, db: &dyn HirDatabase) -> Ty; } impl ProjectionTyExt for ProjectionTy { @@ -347,6 +350,10 @@ impl ProjectionTyExt for ProjectionTy { _ => panic!("projection ty without parent trait"), } } + + fn self_type_parameter(&self, db: &dyn HirDatabase) -> Ty { + self.trait_ref(db).self_type_parameter(Interner) + } } pub trait TraitRefExt { diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs index 6ecb6e6fd1..2c0c6e0b83 100644 --- a/crates/hir-ty/src/consteval.rs +++ b/crates/hir-ty/src/consteval.rs @@ -7,14 +7,17 @@ use std::{ use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData, IntTy, Scalar}; use hir_def::{ + builtin_type::BuiltinInt, expr::{ArithOp, BinaryOp, Expr, ExprId, Literal, Pat, PatId}, path::ModPath, resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs}, + src::HasChildSource, type_ref::ConstScalar, - ConstId, DefWithBodyId, + ConstId, DefWithBodyId, EnumVariantId, Lookup, }; -use la_arena::{Arena, Idx}; +use la_arena::{Arena, Idx, RawIdx}; use stdx::never; +use syntax::ast::HasName; use crate::{ db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, to_placeholder_idx, @@ -77,6 +80,7 @@ pub enum ConstEvalError { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ComputedExpr { Literal(Literal), + Enum(String, EnumVariantId, Literal), Tuple(Box<[ComputedExpr]>), } @@ -104,6 +108,7 @@ impl Display for ComputedExpr { Literal::String(x) => std::fmt::Debug::fmt(x, f), Literal::ByteString(x) => std::fmt::Debug::fmt(x, f), }, + ComputedExpr::Enum(name, _, _) => name.fmt(f), ComputedExpr::Tuple(t) => { f.write_char('(')?; for x in &**t { @@ -148,13 +153,51 @@ fn is_valid(scalar: &Scalar, value: i128) -> bool { } } +fn get_name(ctx: &mut ConstEvalCtx<'_>, variant: EnumVariantId) -> String { + let loc = variant.parent.lookup(ctx.db.upcast()); + let children = variant.parent.child_source(ctx.db.upcast()); + let item_tree = loc.id.item_tree(ctx.db.upcast()); + + let variant_name = children.value[variant.local_id].name(); + let enum_name = item_tree[loc.id.value].name.to_string(); + enum_name + "::" + &variant_name.unwrap().to_string() +} + pub fn eval_const( expr_id: ExprId, ctx: &mut ConstEvalCtx<'_>, ) -> Result { + let u128_to_i128 = |it: u128| -> Result { + it.try_into().map_err(|_| ConstEvalError::NotSupported("u128 is too big")) + }; + let expr = &ctx.exprs[expr_id]; match expr { - Expr::Missing => Err(ConstEvalError::IncompleteExpr), + Expr::Missing => match ctx.owner { + // evaluate the implicit variant index of an enum variant without expression + // FIXME: This should return the type of the enum representation + DefWithBodyId::VariantId(variant) => { + let prev_idx: u32 = variant.local_id.into_raw().into(); + let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw); + let value = match prev_idx { + Some(local_id) => { + let prev_variant = EnumVariantId { local_id, parent: variant.parent }; + 1 + match ctx.db.const_eval_variant(prev_variant)? { + ComputedExpr::Literal(Literal::Int(v, _)) => v, + ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, + _ => { + return Err(ConstEvalError::NotSupported( + "Enum can't contain this kind of value", + )) + } + } + } + _ => 0, + }; + Ok(ComputedExpr::Literal(Literal::Int(value, Some(BuiltinInt::I128)))) + } + _ => Err(ConstEvalError::IncompleteExpr), + }, Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())), &Expr::UnaryOp { expr, op } => { let ty = &ctx.expr_ty(expr); @@ -167,9 +210,7 @@ pub fn eval_const( return Ok(ComputedExpr::Literal(Literal::Bool(!b))) } ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => v - .try_into() - .map_err(|_| ConstEvalError::NotSupported("too big u128"))?, + ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, _ => return Err(ConstEvalError::NotSupported("this kind of operator")), }; let r = match ty.kind(Interner) { @@ -198,9 +239,7 @@ pub fn eval_const( hir_def::expr::UnaryOp::Neg => { let v = match ev { ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => v - .try_into() - .map_err(|_| ConstEvalError::NotSupported("too big u128"))?, + ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, _ => return Err(ConstEvalError::NotSupported("this kind of operator")), }; Ok(ComputedExpr::Literal(Literal::Int( @@ -219,16 +258,12 @@ pub fn eval_const( let op = op.ok_or(ConstEvalError::IncompleteExpr)?; let v1 = match lhs { ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => { - v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))? - } + ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, _ => return Err(ConstEvalError::NotSupported("this kind of operator")), }; let v2 = match rhs { ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => { - v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))? - } + ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, _ => return Err(ConstEvalError::NotSupported("this kind of operator")), }; match op { @@ -339,9 +374,22 @@ pub fn eval_const( ValueNs::GenericParam(_) => { Err(ConstEvalError::NotSupported("const generic without substitution")) } + ValueNs::EnumVariantId(id) => match ctx.db.const_eval_variant(id)? { + ComputedExpr::Literal(lit) => { + Ok(ComputedExpr::Enum(get_name(ctx, id), id, lit)) + } + _ => Err(ConstEvalError::NotSupported( + "Enums can't evalute to anything but numbers", + )), + }, _ => Err(ConstEvalError::NotSupported("path that are not const or local")), } } + // FIXME: Handle the cast target + &Expr::Cast { expr, .. } => match eval_const(expr, ctx)? { + ComputedExpr::Enum(_, _, lit) => Ok(ComputedExpr::Literal(lit)), + _ => Err(ConstEvalError::NotSupported("Can't cast these types")), + }, _ => Err(ConstEvalError::NotSupported("This kind of expression")), } } @@ -412,7 +460,15 @@ pub(crate) fn const_eval_recover( Err(ConstEvalError::Loop) } -pub(crate) fn const_eval_query( +pub(crate) fn const_eval_variant_recover( + _: &dyn HirDatabase, + _: &[String], + _: &EnumVariantId, +) -> Result { + Err(ConstEvalError::Loop) +} + +pub(crate) fn const_eval_variant_query( db: &dyn HirDatabase, const_id: ConstId, ) -> Result { @@ -433,6 +489,26 @@ pub(crate) fn const_eval_query( result } +pub(crate) fn const_eval_query_variant( + db: &dyn HirDatabase, + variant_id: EnumVariantId, +) -> Result { + let def = variant_id.into(); + let body = db.body(def); + let infer = &db.infer(def); + eval_const( + body.body_expr, + &mut ConstEvalCtx { + db, + owner: def, + exprs: &body.exprs, + pats: &body.pats, + local_data: HashMap::default(), + infer, + }, + ) +} + pub(crate) fn eval_to_const<'a>( expr: Idx, mode: ParamLoweringMode, diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index 4a052851af..b76506f6eb 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -87,6 +87,49 @@ fn consts() { ); } +#[test] +fn enums() { + check_number( + r#" + enum E { + F1 = 1, + F2 = 2 * E::F1 as u8, + F3 = 3 * E::F2 as u8, + } + const GOAL: i32 = E::F3 as u8; + "#, + 6, + ); + check_number( + r#" + enum E { F1 = 1, F2, } + const GOAL: i32 = E::F2 as u8; + "#, + 2, + ); + check_number( + r#" + enum E { F1, } + const GOAL: i32 = E::F1 as u8; + "#, + 0, + ); + let r = eval_goal( + r#" + enum E { A = 1, } + const GOAL: E = E::A; + "#, + ) + .unwrap(); + match r { + ComputedExpr::Enum(name, _, Literal::Uint(val, _)) => { + assert_eq!(name, "E::A"); + assert_eq!(val, 1); + } + x => panic!("Expected enum but found {:?}", x), + } +} + #[test] fn const_loop() { check_fail( diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 69283e55a4..932fce8356 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use arrayvec::ArrayVec; use base_db::{impl_intern_key, salsa, CrateId, Upcast}; use hir_def::{ - db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, FunctionId, - GenericDefId, ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId, + db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId, + FunctionId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId, }; use la_arena::ArenaMap; @@ -43,10 +43,14 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::invoke(crate::lower::const_param_ty_query)] fn const_param_ty(&self, def: ConstParamId) -> Ty; - #[salsa::invoke(crate::consteval::const_eval_query)] + #[salsa::invoke(crate::consteval::const_eval_variant_query)] #[salsa::cycle(crate::consteval::const_eval_recover)] fn const_eval(&self, def: ConstId) -> Result; + #[salsa::invoke(crate::consteval::const_eval_query_variant)] + #[salsa::cycle(crate::consteval::const_eval_variant_recover)] + fn const_eval_variant(&self, def: EnumVariantId) -> Result; + #[salsa::invoke(crate::lower::impl_trait_query)] fn impl_trait(&self, def: ImplId) -> Option>; @@ -116,6 +120,8 @@ pub trait HirDatabase: DefDatabase + Upcast { fn intern_impl_trait_id(&self, id: ImplTraitId) -> InternedOpaqueTyId; #[salsa::interned] fn intern_closure(&self, id: (DefWithBodyId, ExprId)) -> InternedClosureId; + #[salsa::interned] + fn intern_generator(&self, id: (DefWithBodyId, ExprId)) -> InternedGeneratorId; #[salsa::invoke(chalk_db::associated_ty_data_query)] fn associated_ty_data(&self, id: chalk_db::AssocTypeId) -> Arc; @@ -188,6 +194,9 @@ fn infer_wait(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc DefWithBodyId::ConstId(it) => { db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string() } + DefWithBodyId::VariantId(it) => { + db.enum_data(it.parent).variants[it.local_id].name.to_string() + } }); db.infer_query(def) } @@ -226,6 +235,10 @@ impl_intern_key!(InternedOpaqueTyId); pub struct InternedClosureId(salsa::InternId); impl_intern_key!(InternedClosureId); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct InternedGeneratorId(salsa::InternId); +impl_intern_key!(InternedGeneratorId); + /// This exists just for Chalk, because Chalk just has a single `FnDefId` where /// we have different IDs for struct and enum variant constructors. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 161b19a739..431ab949b4 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -18,7 +18,9 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec { let is_unsafe = match def { DefWithBodyId::FunctionId(it) => db.function_data(it).has_unsafe_kw(), - DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false, + DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) | DefWithBodyId::VariantId(_) => { + false + } }; if is_unsafe { return res; diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 874abdaea8..0221f922fe 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -20,13 +20,14 @@ use hir_def::{ }; use hir_expand::{hygiene::Hygiene, name::Name}; use itertools::Itertools; +use smallvec::SmallVec; use syntax::SmolStr; use crate::{ db::HirDatabase, from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, lt_from_placeholder_idx, mapping::from_chalk, - primitive, subst_prefix, to_assoc_type_id, + primitive, to_assoc_type_id, utils::{self, generics}, AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstValue, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability, @@ -221,6 +222,7 @@ pub enum DisplaySourceCodeError { PathNotFound, UnknownType, Closure, + Generator, } pub enum HirDisplayError { @@ -289,7 +291,7 @@ impl HirDisplay for ProjectionTy { let trait_ = f.db.trait_data(self.trait_(f.db)); write!(f, "<")?; - self.self_type_parameter(Interner).hir_fmt(f)?; + self.self_type_parameter(f.db).hir_fmt(f)?; write!(f, " as {}", trait_.name)?; if self.substitution.len(Interner) > 1 { write!(f, "<")?; @@ -504,8 +506,15 @@ impl HirDisplay for Ty { let total_len = parent_params + self_param + type_params + const_params; // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self? if total_len > 0 { + // `parameters` are in the order of fn's params (including impl traits), + // parent's params (those from enclosing impl or trait, if any). + let parameters = parameters.as_slice(Interner); + let fn_params_len = self_param + type_params + const_params; + let fn_params = parameters.get(..fn_params_len); + let parent_params = parameters.get(parameters.len() - parent_params..); + let params = parent_params.into_iter().chain(fn_params).flatten(); write!(f, "<")?; - f.write_joined(¶meters.as_slice(Interner)[..total_len], ", ")?; + f.write_joined(params, ", ")?; write!(f, ">")?; } } @@ -577,9 +586,8 @@ impl HirDisplay for Ty { Some(x) => x, None => return true, }; - let actual_default = default_parameter - .clone() - .substitute(Interner, &subst_prefix(parameters, i)); + let actual_default = + default_parameter.clone().substitute(Interner, ¶meters); parameter != &actual_default } let mut default_from = 0; @@ -723,7 +731,7 @@ impl HirDisplay for Ty { WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(proj), ty: _, - }) => &proj.self_type_parameter(Interner) == self, + }) => &proj.self_type_parameter(f.db) == self, _ => false, }) .collect::>(); @@ -743,9 +751,19 @@ impl HirDisplay for Ty { } TyKind::BoundVar(idx) => idx.hir_fmt(f)?, TyKind::Dyn(dyn_ty) => { + // Reorder bounds to satisfy `write_bounds_like_dyn_trait()`'s expectation. + // FIXME: `Iterator::partition_in_place()` or `Vec::drain_filter()` may make it + // more efficient when either of them hits stable. + let mut bounds: SmallVec<[_; 4]> = + dyn_ty.bounds.skip_binders().iter(Interner).cloned().collect(); + let (auto_traits, others): (SmallVec<[_; 4]>, _) = + bounds.drain(1..).partition(|b| b.skip_binders().trait_id().is_some()); + bounds.extend(others); + bounds.extend(auto_traits); + write_bounds_like_dyn_trait_with_prefix( "dyn", - dyn_ty.bounds.skip_binders().interned(), + &bounds, SizedByDefault::NotSized, f, )?; @@ -783,7 +801,34 @@ impl HirDisplay for Ty { write!(f, "{{unknown}}")?; } TyKind::InferenceVar(..) => write!(f, "_")?, - TyKind::Generator(..) => write!(f, "{{generator}}")?, + TyKind::Generator(_, subst) => { + if f.display_target.is_source_code() { + return Err(HirDisplayError::DisplaySourceCodeError( + DisplaySourceCodeError::Generator, + )); + } + + let subst = subst.as_slice(Interner); + let a: Option> = subst + .get(subst.len() - 3..) + .map(|args| args.iter().map(|arg| arg.ty(Interner)).collect()) + .flatten(); + + if let Some([resume_ty, yield_ty, ret_ty]) = a.as_deref() { + write!(f, "|")?; + resume_ty.hir_fmt(f)?; + write!(f, "|")?; + + write!(f, " yields ")?; + yield_ty.hir_fmt(f)?; + + write!(f, " -> ")?; + ret_ty.hir_fmt(f)?; + } else { + // This *should* be unreachable, but fallback just in case. + write!(f, "{{generator}}")?; + } + } TyKind::GeneratorWitness(..) => write!(f, "{{generator witness}}")?, } Ok(()) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index e37763e8ea..6a5c4966f7 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -2,7 +2,7 @@ //! the type of each expression and pattern. //! //! For type inference, compare the implementations in rustc (the various -//! check_* methods in librustc_typeck/check/mod.rs are a good entry point) and +//! check_* methods in rustc_hir_analysis/check/mod.rs are a good entry point) and //! IntelliJ-Rust (org.rust.lang.core.types.infer). Our entry point for //! inference here is the `infer` function, which infers the types of all //! expressions in a given function. @@ -19,14 +19,15 @@ use std::sync::Arc; use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags}; use hir_def::{ body::Body, + builtin_type::BuiltinType, data::{ConstData, StaticData}, expr::{BindingAnnotation, ExprId, PatId}, lang_item::LangItemTarget, path::{path, Path}, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, type_ref::TypeRef, - AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup, - TraitId, TypeAliasId, VariantId, + AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, + ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId, }; use hir_expand::name::{name, Name}; use itertools::Either; @@ -67,6 +68,12 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc ctx.collect_const(&db.const_data(c)), DefWithBodyId::FunctionId(f) => ctx.collect_fn(f), DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)), + DefWithBodyId::VariantId(v) => { + ctx.return_ty = TyBuilder::builtin(match db.enum_data(v.parent).variant_body_type() { + Either::Left(builtin) => BuiltinType::Int(builtin), + Either::Right(builtin) => BuiltinType::Uint(builtin), + }); + } } ctx.infer_body(); @@ -183,7 +190,9 @@ pub(crate) type InferResult = Result, TypeError>; pub enum InferenceDiagnostic { NoSuchField { expr: ExprId }, BreakOutsideOfLoop { expr: ExprId, is_break: bool }, + IncorrectTryTarget { expr: ExprId }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, + DoesNotImplement { expr: ExprId, trait_: TraitId, ty: Ty }, } /// A mismatch between an expected and an inferred type. @@ -332,7 +341,7 @@ pub struct InferenceResult { /// unresolved or missing subpatterns or subpatterns of mismatched types. pub type_of_pat: ArenaMap, type_mismatches: FxHashMap, - /// Interned Unknown to return references to. + /// Interned common types to return references to. standard_types: InternedStandardTypes, /// Stores the types which were implicitly dereferenced in pattern binding modes. pub pat_adjustments: FxHashMap>, @@ -412,6 +421,8 @@ pub(crate) struct InferenceContext<'a> { /// closures, but currently this is the only field that will change there, /// so it doesn't make sense. return_ty: Ty, + /// The resume type and the yield type, respectively, of the generator being inferred. + resume_yield_tys: Option<(Ty, Ty)>, diverges: Diverges, breakables: Vec, } @@ -476,6 +487,7 @@ impl<'a> InferenceContext<'a> { table: unify::InferenceTable::new(db, trait_env.clone()), trait_env, return_ty: TyKind::Error.intern(Interner), // set in collect_fn_signature + resume_yield_tys: None, db, owner, body, @@ -703,6 +715,8 @@ impl<'a> InferenceContext<'a> { &mut self, inner_ty: Ty, assoc_ty: Option, + // FIXME(GATs): these are args for the trait ref, args for assoc type itself should be + // handled when we support them. params: &[GenericArg], ) -> Ty { match assoc_ty { @@ -794,7 +808,18 @@ impl<'a> InferenceContext<'a> { self.resolve_variant_on_alias(ty, unresolved, path) } TypeNs::TypeAliasId(it) => { - let ty = TyBuilder::def_ty(self.db, it.into()) + let container = it.lookup(self.db.upcast()).container; + let parent_subst = match container { + ItemContainerId::TraitId(id) => { + let subst = TyBuilder::subst_for_def(self.db, id, None) + .fill_with_inference_vars(&mut self.table) + .build(); + Some(subst) + } + // Type aliases do not exist in impls. + _ => None, + }; + let ty = TyBuilder::def_ty(self.db, it.into(), parent_subst) .fill_with_inference_vars(&mut self.table) .build(); self.resolve_variant_on_alias(ty, unresolved, path) @@ -873,18 +898,13 @@ impl<'a> InferenceContext<'a> { fn resolve_into_iter_item(&self) -> Option { let path = path![core::iter::IntoIterator]; let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name![Item]) + self.db.trait_data(trait_).associated_type_by_name(&name![IntoIter]) } - fn resolve_ops_try_ok(&self) -> Option { - // FIXME resolve via lang_item once try v2 is stable - let path = path![core::ops::Try]; + fn resolve_iterator_item(&self) -> Option { + let path = path![core::iter::Iterator]; let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; - let trait_data = self.db.trait_data(trait_); - trait_data - // FIXME remove once try v2 is stable - .associated_type_by_name(&name![Ok]) - .or_else(|| trait_data.associated_type_by_name(&name![Output])) + self.db.trait_data(trait_).associated_type_by_name(&name![Item]) } fn resolve_ops_neg_output(&self) -> Option { diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 3ead929098..094e460dbf 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -12,6 +12,7 @@ use crate::{ use super::{Expectation, InferenceContext}; impl InferenceContext<'_> { + // This function handles both closures and generators. pub(super) fn deduce_closure_type_from_expectations( &mut self, closure_expr: ExprId, @@ -27,6 +28,11 @@ impl InferenceContext<'_> { // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here. let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty); + // Generators are not Fn* so return early. + if matches!(closure_ty.kind(Interner), TyKind::Generator(..)) { + return; + } + // Deduction based on the expected `dyn Fn` is done separately. if let TyKind::Dyn(dyn_ty) = expected_ty.kind(Interner) { if let Some(sig) = self.deduce_sig_from_dyn_ty(dyn_ty) { diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index f54440bf5b..8df25c83c6 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -3,7 +3,7 @@ //! like going from `&Vec` to `&[T]`. //! //! See and -//! `librustc_typeck/check/coercion.rs`. +//! `rustc_hir_analysis/check/coercion.rs`. use std::{iter, sync::Arc}; diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 2d04a864a2..59ab50d071 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -10,30 +10,33 @@ use chalk_ir::{ cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind, }; use hir_def::{ - expr::{ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, LabelId, Literal, Statement, UnaryOp}, + expr::{ + ArithOp, Array, BinaryOp, ClosureKind, CmpOp, Expr, ExprId, LabelId, Literal, Statement, + UnaryOp, + }, generics::TypeOrConstParamData, path::{GenericArg, GenericArgs}, resolver::resolver_for_expr, ConstParamId, FieldId, ItemContainerId, Lookup, }; -use hir_expand::name::Name; +use hir_expand::{name, name::Name}; use stdx::always; use syntax::ast::RangeOp; use crate::{ autoderef::{self, Autoderef}, consteval, - infer::{coerce::CoerceMany, find_continuable, BreakableKind}, + infer::{coerce::CoerceMany, find_continuable, path, BreakableKind}, lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, mapping::{from_chalk, ToChalk}, method_resolution::{self, lang_names_for_bin_op, VisibleFromModule}, primitive::{self, UintTy}, - static_lifetime, to_chalk_trait_id, + static_lifetime, to_assoc_type_id, to_chalk_trait_id, utils::{generics, Generics}, - AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar, - Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind, + AdtId, AliasEq, AliasTy, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, + ProjectionTy, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind, }; use super::{ @@ -204,8 +207,10 @@ impl<'a> InferenceContext<'a> { } &Expr::For { iterable, body, pat, label } => { let iterable_ty = self.infer_expr(iterable, &Expectation::none()); - let pat_ty = + let into_iter_ty = self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); + let pat_ty = + self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item()); self.infer_pat(pat, &pat_ty, BindingMode::default()); self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { @@ -216,7 +221,7 @@ impl<'a> InferenceContext<'a> { self.diverges = Diverges::Maybe; TyBuilder::unit() } - Expr::Closure { body, args, ret_type, arg_types } => { + Expr::Closure { body, args, ret_type, arg_types, closure_kind } => { assert_eq!(args.len(), arg_types.len()); let mut sig_tys = Vec::new(); @@ -244,20 +249,40 @@ impl<'a> InferenceContext<'a> { ), }) .intern(Interner); - let closure_id = self.db.intern_closure((self.owner, tgt_expr)).into(); - let closure_ty = - TyKind::Closure(closure_id, Substitution::from1(Interner, sig_ty.clone())) - .intern(Interner); + + let (ty, resume_yield_tys) = if matches!(closure_kind, ClosureKind::Generator(_)) { + // FIXME: report error when there are more than 1 parameter. + let resume_ty = match sig_tys.first() { + // When `sig_tys.len() == 1` the first type is the return type, not the + // first parameter type. + Some(ty) if sig_tys.len() > 1 => ty.clone(), + _ => self.result.standard_types.unit.clone(), + }; + let yield_ty = self.table.new_type_var(); + + let subst = TyBuilder::subst_for_generator(self.db, self.owner) + .push(resume_ty.clone()) + .push(yield_ty.clone()) + .push(ret_ty.clone()) + .build(); + + let generator_id = self.db.intern_generator((self.owner, tgt_expr)).into(); + let generator_ty = TyKind::Generator(generator_id, subst).intern(Interner); + + (generator_ty, Some((resume_ty, yield_ty))) + } else { + let closure_id = self.db.intern_closure((self.owner, tgt_expr)).into(); + let closure_ty = + TyKind::Closure(closure_id, Substitution::from1(Interner, sig_ty.clone())) + .intern(Interner); + + (closure_ty, None) + }; // Eagerly try to relate the closure type with the expected // type, otherwise we often won't have enough information to // infer the body. - self.deduce_closure_type_from_expectations( - tgt_expr, - &closure_ty, - &sig_ty, - expected, - ); + self.deduce_closure_type_from_expectations(tgt_expr, &ty, &sig_ty, expected); // Now go through the argument patterns for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { @@ -266,6 +291,8 @@ impl<'a> InferenceContext<'a> { let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); + let prev_resume_yield_tys = + mem::replace(&mut self.resume_yield_tys, resume_yield_tys); self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); @@ -273,8 +300,9 @@ impl<'a> InferenceContext<'a> { self.diverges = prev_diverges; self.return_ty = prev_ret_ty; + self.resume_yield_tys = prev_resume_yield_tys; - closure_ty + ty } Expr::Call { callee, args, .. } => { let callee_ty = self.infer_expr(*callee, &Expectation::none()); @@ -423,11 +451,18 @@ impl<'a> InferenceContext<'a> { TyKind::Never.intern(Interner) } Expr::Yield { expr } => { - // FIXME: track yield type for coercion - if let Some(expr) = expr { - self.infer_expr(*expr, &Expectation::none()); + if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { + if let Some(expr) = expr { + self.infer_expr_coerce(*expr, &Expectation::has_type(yield_ty)); + } else { + let unit = self.result.standard_types.unit.clone(); + let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty); + } + resume_ty + } else { + // FIXME: report error (yield expr in non-generator) + TyKind::Error.intern(Interner) } - TyKind::Never.intern(Interner) } Expr::RecordLit { path, fields, spread, .. } => { let (ty, def_id) = self.resolve_variant(path.as_deref(), false); @@ -529,9 +564,29 @@ impl<'a> InferenceContext<'a> { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); self.resolve_associated_type(inner_ty, self.resolve_future_future_output()) } - Expr::Try { expr } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); - self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok()) + &Expr::Try { expr } => { + let inner_ty = self.infer_expr_inner(expr, &Expectation::none()); + match self.resolve_try_impl_for(inner_ty.clone()) { + Some((_, Some((output, residual)))) => { + if let Some((_trait, false)) = + self.implements_from_residual(self.return_ty.clone(), residual) + { + self.push_diagnostic(InferenceDiagnostic::IncorrectTryTarget { + expr: tgt_expr, + }); + } + output + } + Some((trait_, None)) => { + self.push_diagnostic(InferenceDiagnostic::DoesNotImplement { + expr, + trait_, + ty: inner_ty, + }); + self.err_ty() + } + None => self.err_ty(), + } } Expr::Cast { expr, type_ref } => { // FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary) @@ -952,11 +1007,13 @@ impl<'a> InferenceContext<'a> { let lhs_ty = self.infer_expr(lhs, &lhs_expectation); let rhs_ty = self.table.new_type_var(); - let func = lang_names_for_bin_op(op).and_then(|(name, lang_item)| { - self.db.trait_data(self.resolve_lang_item(lang_item)?.as_trait()?).method_by_name(&name) + let trait_func = lang_names_for_bin_op(op).and_then(|(name, lang_item)| { + let trait_id = self.resolve_lang_item(lang_item)?.as_trait()?; + let func = self.db.trait_data(trait_id).method_by_name(&name)?; + Some((trait_id, func)) }); - let func = match func { - Some(func) => func, + let (trait_, func) = match trait_func { + Some(it) => it, None => { let rhs_ty = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone()); let rhs_ty = self.infer_expr_coerce(rhs, &Expectation::from_option(rhs_ty)); @@ -966,7 +1023,9 @@ impl<'a> InferenceContext<'a> { } }; - let subst = TyBuilder::subst_for_def(self.db, func) + // HACK: We can use this substitution for the function because the function itself doesn't + // have its own generic parameters. + let subst = TyBuilder::subst_for_def(self.db, trait_, None) .push(lhs_ty.clone()) .push(rhs_ty.clone()) .build(); @@ -1245,19 +1304,7 @@ impl<'a> InferenceContext<'a> { assert_eq!(self_params, 0); // method shouldn't have another Self param let total_len = parent_params + type_params + const_params + impl_trait_params; let mut substs = Vec::with_capacity(total_len); - // Parent arguments are unknown - for (id, param) in def_generics.iter_parent() { - match param { - TypeOrConstParamData::TypeParamData(_) => { - substs.push(GenericArgData::Ty(self.table.new_type_var()).intern(Interner)); - } - TypeOrConstParamData::ConstParamData(_) => { - let ty = self.db.const_param_ty(ConstParamId::from_unchecked(id)); - substs - .push(GenericArgData::Const(self.table.new_const_var(ty)).intern(Interner)); - } - } - } + // handle provided arguments if let Some(generic_args) = generic_args { // if args are provided, it should be all of them, but we can't rely on that @@ -1266,7 +1313,7 @@ impl<'a> InferenceContext<'a> { .iter() .filter(|arg| !matches!(arg, GenericArg::Lifetime(_))) .take(type_params + const_params) - .zip(def_generics.iter_id().skip(parent_params)) + .zip(def_generics.iter_id()) { if let Some(g) = generic_arg_to_chalk( self.db, @@ -1290,6 +1337,9 @@ impl<'a> InferenceContext<'a> { } } }; + + // Handle everything else as unknown. This also handles generic arguments for the method's + // parent (impl or trait), which should come after those for the method. for (id, data) in def_generics.iter().skip(substs.len()) { match data { TypeOrConstParamData::TypeParamData(_) => { @@ -1327,9 +1377,13 @@ impl<'a> InferenceContext<'a> { CallableDefId::FunctionId(f) => { if let ItemContainerId::TraitId(trait_) = f.lookup(self.db.upcast()).container { // construct a TraitRef - let substs = crate::subst_prefix( - &*parameters, - generics(self.db.upcast(), trait_.into()).len(), + let params_len = parameters.len(Interner); + let trait_params_len = generics(self.db.upcast(), trait_.into()).len(); + let substs = Substitution::from_iter( + Interner, + // The generic parameters for the trait come after those for the + // function. + ¶meters.as_slice(Interner)[params_len - trait_params_len..], ); self.push_obligation( TraitRef { trait_id: to_chalk_trait_id(trait_), substitution: substs } @@ -1496,4 +1550,67 @@ impl<'a> InferenceContext<'a> { let ctx = self.breakables.pop().expect("breakable stack broken"); (ctx.may_break.then(|| ctx.coerce.complete()), res) } + + /// Check whether `ty` implements `FromResidual` + fn implements_from_residual(&mut self, ty: Ty, r: Ty) -> Option<(hir_def::TraitId, bool)> { + let from_residual_trait = self + .resolver + .resolve_known_trait(self.db.upcast(), &(super::path![core::ops::FromResidual]))?; + let r = GenericArgData::Ty(r).intern(Interner); + let b = TyBuilder::trait_ref(self.db, from_residual_trait); + if b.remaining() != 2 { + return Some((from_residual_trait, false)); + } + let trait_ref = b.push(ty).push(r).build(); + Some((from_residual_trait, self.table.try_obligation(trait_ref.cast(Interner)).is_some())) + } + + fn resolve_try_impl_for(&mut self, ty: Ty) -> Option<(hir_def::TraitId, Option<(Ty, Ty)>)> { + let path = path![core::ops::Try]; + let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; + + let trait_ref = TyBuilder::trait_ref(self.db, trait_).push(ty).build(); + let substitution = trait_ref.substitution.clone(); + self.push_obligation(trait_ref.clone().cast(Interner)); + + let trait_data = self.db.trait_data(trait_); + let output = trait_data.associated_type_by_name(&name![Output]); + let residual = trait_data.associated_type_by_name(&name![Residual]); + + let output_ty = match output { + Some(output) => { + let output_ty = self.table.new_type_var(); + let alias_eq = AliasEq { + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: to_assoc_type_id(output), + substitution: substitution.clone(), + }), + ty: output_ty.clone(), + }; + self.push_obligation(alias_eq.cast(Interner)); + output_ty + } + None => self.err_ty(), + }; + let residual_ty = match residual { + Some(residual) => { + let residual_ty = self.table.new_type_var(); + let alias_eq = AliasEq { + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: to_assoc_type_id(residual), + substitution, + }), + ty: residual_ty.clone(), + }; + self.push_obligation(alias_eq.cast(Interner)); + residual_ty + } + None => self.err_ty(), + }; + // FIXME: We are doing the work twice here I think? + Some(( + trait_, + self.table.try_obligation(trait_ref.cast(Interner)).map(|_| (output_ty, residual_ty)), + )) + } } diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index f580e09e91..7a4754cdc7 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -12,8 +12,8 @@ use crate::{ builder::ParamKind, consteval, method_resolution::{self, VisibleFromModule}, - GenericArgData, Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, - ValueTyDefId, + utils::generics, + Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, ValueTyDefId, }; use super::{ExprOrPatId, InferenceContext, TraitRef}; @@ -96,17 +96,21 @@ impl<'a> InferenceContext<'a> { ValueNs::GenericParam(it) => return Some(self.db.const_param_ty(it)), }; - let parent_substs = self_subst.unwrap_or_else(|| Substitution::empty(Interner)); let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); let substs = ctx.substs_from_path(path, typable, true); - let mut it = substs.as_slice(Interner)[parent_substs.len(Interner)..].iter().cloned(); - let ty = TyBuilder::value_ty(self.db, typable) - .use_parent_substs(&parent_substs) + let substs = substs.as_slice(Interner); + let parent_substs = self_subst.or_else(|| { + let generics = generics(self.db.upcast(), typable.to_generic_def_id()?); + let parent_params_len = generics.parent_generics()?.len(); + let parent_args = &substs[substs.len() - parent_params_len..]; + Some(Substitution::from_iter(Interner, parent_args)) + }); + let parent_substs_len = parent_substs.as_ref().map_or(0, |s| s.len(Interner)); + let mut it = substs.iter().take(substs.len() - parent_substs_len).cloned(); + let ty = TyBuilder::value_ty(self.db, typable, parent_substs) .fill(|x| { it.next().unwrap_or_else(|| match x { - ParamKind::Type => { - GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) - } + ParamKind::Type => TyKind::Error.intern(Interner).cast(Interner), ParamKind::Const(ty) => consteval::unknown_const_as_generic(ty.clone()), }) }) @@ -249,7 +253,7 @@ impl<'a> InferenceContext<'a> { }; let substs = match container { ItemContainerId::ImplId(impl_id) => { - let impl_substs = TyBuilder::subst_for_def(self.db, impl_id) + let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None) .fill_with_inference_vars(&mut self.table) .build(); let impl_self_ty = diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index e77b55670b..b00e3216b2 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -4,7 +4,7 @@ use std::{fmt, mem, sync::Arc}; use chalk_ir::{ cast::Cast, fold::TypeFoldable, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy, - IntTy, NoSolution, TyVariableKind, UniverseIndex, + IntTy, TyVariableKind, UniverseIndex, }; use chalk_solve::infer::ParameterEnaVariableExt; use ena::unify::UnifyKey; @@ -331,7 +331,6 @@ impl<'a> InferenceTable<'a> { &mut resolve::Resolver { table: self, var_stack, fallback }, DebruijnIndex::INNERMOST, ) - .expect("fold failed unexpectedly") } pub(crate) fn resolve_completely(&mut self, t: T) -> T @@ -452,13 +451,14 @@ impl<'a> InferenceTable<'a> { f: impl FnOnce(&mut Self) -> T, ) -> T { use chalk_ir::fold::TypeFolder; + + #[derive(chalk_derive::FallibleTypeFolder)] + #[has_interner(Interner)] struct VarFudger<'a, 'b> { table: &'a mut InferenceTable<'b>, highest_known_var: InferenceVar, } impl<'a, 'b> TypeFolder for VarFudger<'a, 'b> { - type Error = NoSolution; - fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } @@ -472,24 +472,24 @@ impl<'a> InferenceTable<'a> { var: chalk_ir::InferenceVar, kind: TyVariableKind, _outer_binder: chalk_ir::DebruijnIndex, - ) -> chalk_ir::Fallible> { - Ok(if var < self.highest_known_var { + ) -> chalk_ir::Ty { + if var < self.highest_known_var { var.to_ty(Interner, kind) } else { self.table.new_type_var() - }) + } } fn fold_inference_lifetime( &mut self, var: chalk_ir::InferenceVar, _outer_binder: chalk_ir::DebruijnIndex, - ) -> chalk_ir::Fallible> { - Ok(if var < self.highest_known_var { + ) -> chalk_ir::Lifetime { + if var < self.highest_known_var { var.to_lifetime(Interner) } else { self.table.new_lifetime_var() - }) + } } fn fold_inference_const( @@ -497,12 +497,12 @@ impl<'a> InferenceTable<'a> { ty: chalk_ir::Ty, var: chalk_ir::InferenceVar, _outer_binder: chalk_ir::DebruijnIndex, - ) -> chalk_ir::Fallible> { - Ok(if var < self.highest_known_var { + ) -> chalk_ir::Const { + if var < self.highest_known_var { var.to_const(Interner, ty) } else { self.table.new_const_var(ty) - }) + } } } @@ -512,7 +512,6 @@ impl<'a> InferenceTable<'a> { self.rollback_to(snapshot); result .fold_with(&mut VarFudger { table: self, highest_known_var }, DebruijnIndex::INNERMOST) - .expect("fold_with with VarFudger") } /// This checks whether any of the free variables in the `canonicalized` @@ -598,11 +597,14 @@ impl<'a> InferenceTable<'a> { .build(); let projection = { - let b = TyBuilder::assoc_type_projection(self.db, output_assoc_type); + let b = TyBuilder::subst_for_def(self.db, fn_once_trait, None); if b.remaining() != 2 { return None; } - b.push(ty.clone()).push(arg_ty).build() + let fn_once_subst = b.push(ty.clone()).push(arg_ty).build(); + + TyBuilder::assoc_type_projection(self.db, output_assoc_type, Some(fn_once_subst)) + .build() }; let trait_env = self.trait_env.env.clone(); @@ -636,21 +638,24 @@ mod resolve { use chalk_ir::{ cast::Cast, fold::{TypeFoldable, TypeFolder}, - Fallible, NoSolution, }; use hir_def::type_ref::ConstScalar; - pub(super) struct Resolver<'a, 'b, F> { + #[derive(chalk_derive::FallibleTypeFolder)] + #[has_interner(Interner)] + pub(super) struct Resolver< + 'a, + 'b, + F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg, + > { pub(super) table: &'a mut InferenceTable<'b>, pub(super) var_stack: &'a mut Vec, pub(super) fallback: F, } - impl<'a, 'b, 'i, F> TypeFolder for Resolver<'a, 'b, F> + impl<'a, 'b, F> TypeFolder for Resolver<'a, 'b, F> where - F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg + 'i, + F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg, { - type Error = NoSolution; - fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } @@ -664,20 +669,19 @@ mod resolve { var: InferenceVar, kind: TyVariableKind, outer_binder: DebruijnIndex, - ) -> Fallible { + ) -> Ty { let var = self.table.var_unification_table.inference_var_root(var); if self.var_stack.contains(&var) { // recursive type let default = self.table.fallback_value(var, kind).cast(Interner); - return Ok((self.fallback)(var, VariableKind::Ty(kind), default, outer_binder) + return (self.fallback)(var, VariableKind::Ty(kind), default, outer_binder) .assert_ty_ref(Interner) - .clone()); + .clone(); } let result = if let Some(known_ty) = self.table.var_unification_table.probe_var(var) { // known_ty may contain other variables that are known by now self.var_stack.push(var); - let result = - known_ty.fold_with(self, outer_binder).expect("fold failed unexpectedly"); + let result = known_ty.fold_with(self, outer_binder); self.var_stack.pop(); result.assert_ty_ref(Interner).clone() } else { @@ -686,7 +690,7 @@ mod resolve { .assert_ty_ref(Interner) .clone() }; - Ok(result) + result } fn fold_inference_const( @@ -694,7 +698,7 @@ mod resolve { ty: Ty, var: InferenceVar, outer_binder: DebruijnIndex, - ) -> Fallible { + ) -> Const { let var = self.table.var_unification_table.inference_var_root(var); let default = ConstData { ty: ty.clone(), @@ -704,35 +708,33 @@ mod resolve { .cast(Interner); if self.var_stack.contains(&var) { // recursive - return Ok((self.fallback)(var, VariableKind::Const(ty), default, outer_binder) + return (self.fallback)(var, VariableKind::Const(ty), default, outer_binder) .assert_const_ref(Interner) - .clone()); + .clone(); } - let result = if let Some(known_ty) = self.table.var_unification_table.probe_var(var) { + if let Some(known_ty) = self.table.var_unification_table.probe_var(var) { // known_ty may contain other variables that are known by now self.var_stack.push(var); - let result = - known_ty.fold_with(self, outer_binder).expect("fold failed unexpectedly"); + let result = known_ty.fold_with(self, outer_binder); self.var_stack.pop(); result.assert_const_ref(Interner).clone() } else { (self.fallback)(var, VariableKind::Const(ty), default, outer_binder) .assert_const_ref(Interner) .clone() - }; - Ok(result) + } } fn fold_inference_lifetime( &mut self, _var: InferenceVar, _outer_binder: DebruijnIndex, - ) -> Fallible { + ) -> Lifetime { // fall back all lifetimes to 'static -- currently we don't deal // with any lifetimes, but we can sometimes get some lifetime // variables through Chalk's unification, and this at least makes // sure we don't leak them outside of inference - Ok(crate::static_lifetime()) + crate::static_lifetime() } } } diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index de4a5446e5..c4b700cbce 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -254,13 +254,13 @@ impl CallableSig { } impl TypeFoldable for CallableSig { - fn fold_with( + fn try_fold_with( self, - folder: &mut dyn chalk_ir::fold::TypeFolder, + folder: &mut dyn chalk_ir::fold::FallibleTypeFolder, outer_binder: DebruijnIndex, ) -> Result { let vec = self.params_and_return.to_vec(); - let folded = vec.fold_with(folder, outer_binder)?; + let folded = vec.try_fold_with(folder, outer_binder)?; Ok(CallableSig { params_and_return: folded.into(), is_varargs: self.is_varargs }) } } @@ -292,16 +292,19 @@ pub(crate) fn fold_free_vars + TypeFoldable< for_ty: impl FnMut(BoundVar, DebruijnIndex) -> Ty, for_const: impl FnMut(Ty, BoundVar, DebruijnIndex) -> Const, ) -> T { - use chalk_ir::{fold::TypeFolder, Fallible}; - struct FreeVarFolder(F1, F2); + use chalk_ir::fold::TypeFolder; + + #[derive(chalk_derive::FallibleTypeFolder)] + #[has_interner(Interner)] + struct FreeVarFolder< + F1: FnMut(BoundVar, DebruijnIndex) -> Ty, + F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const, + >(F1, F2); impl< - 'i, - F1: FnMut(BoundVar, DebruijnIndex) -> Ty + 'i, - F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const + 'i, + F1: FnMut(BoundVar, DebruijnIndex) -> Ty, + F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const, > TypeFolder for FreeVarFolder { - type Error = NoSolution; - fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } @@ -310,12 +313,8 @@ pub(crate) fn fold_free_vars + TypeFoldable< Interner } - fn fold_free_var_ty( - &mut self, - bound_var: BoundVar, - outer_binder: DebruijnIndex, - ) -> Fallible { - Ok(self.0(bound_var, outer_binder)) + fn fold_free_var_ty(&mut self, bound_var: BoundVar, outer_binder: DebruijnIndex) -> Ty { + self.0(bound_var, outer_binder) } fn fold_free_var_const( @@ -323,12 +322,11 @@ pub(crate) fn fold_free_vars + TypeFoldable< ty: Ty, bound_var: BoundVar, outer_binder: DebruijnIndex, - ) -> Fallible { - Ok(self.1(ty, bound_var, outer_binder)) + ) -> Const { + self.1(ty, bound_var, outer_binder) } } t.fold_with(&mut FreeVarFolder(for_ty, for_const), DebruijnIndex::INNERMOST) - .expect("fold failed unexpectedly") } pub(crate) fn fold_tys + TypeFoldable>( @@ -351,16 +349,13 @@ pub(crate) fn fold_tys_and_consts + TypeFold f: impl FnMut(Either, DebruijnIndex) -> Either, binders: DebruijnIndex, ) -> T { - use chalk_ir::{ - fold::{TypeFolder, TypeSuperFoldable}, - Fallible, - }; - struct TyFolder(F); - impl<'i, F: FnMut(Either, DebruijnIndex) -> Either + 'i> - TypeFolder for TyFolder + use chalk_ir::fold::{TypeFolder, TypeSuperFoldable}; + #[derive(chalk_derive::FallibleTypeFolder)] + #[has_interner(Interner)] + struct TyFolder, DebruijnIndex) -> Either>(F); + impl, DebruijnIndex) -> Either> TypeFolder + for TyFolder { - type Error = NoSolution; - fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } @@ -369,16 +364,16 @@ pub(crate) fn fold_tys_and_consts + TypeFold Interner } - fn fold_ty(&mut self, ty: Ty, outer_binder: DebruijnIndex) -> Fallible { - let ty = ty.super_fold_with(self.as_dyn(), outer_binder)?; - Ok(self.0(Either::Left(ty), outer_binder).left().unwrap()) + fn fold_ty(&mut self, ty: Ty, outer_binder: DebruijnIndex) -> Ty { + let ty = ty.super_fold_with(self.as_dyn(), outer_binder); + self.0(Either::Left(ty), outer_binder).left().unwrap() } - fn fold_const(&mut self, c: Const, outer_binder: DebruijnIndex) -> Fallible { - Ok(self.0(Either::Right(c), outer_binder).right().unwrap()) + fn fold_const(&mut self, c: Const, outer_binder: DebruijnIndex) -> Const { + self.0(Either::Right(c), outer_binder).right().unwrap() } } - t.fold_with(&mut TyFolder(f), binders).expect("fold failed unexpectedly") + t.fold_with(&mut TyFolder(f), binders) } /// 'Canonicalizes' the `t` by replacing any errors with new variables. Also @@ -390,16 +385,16 @@ where T: HasInterner, { use chalk_ir::{ - fold::{TypeFolder, TypeSuperFoldable}, + fold::{FallibleTypeFolder, TypeSuperFoldable}, Fallible, }; struct ErrorReplacer { vars: usize, } - impl TypeFolder for ErrorReplacer { + impl FallibleTypeFolder for ErrorReplacer { type Error = NoSolution; - fn as_dyn(&mut self) -> &mut dyn TypeFolder { + fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder { self } @@ -407,18 +402,17 @@ where Interner } - fn fold_ty(&mut self, ty: Ty, outer_binder: DebruijnIndex) -> Fallible { + fn try_fold_ty(&mut self, ty: Ty, outer_binder: DebruijnIndex) -> Fallible { if let TyKind::Error = ty.kind(Interner) { let index = self.vars; self.vars += 1; Ok(TyKind::BoundVar(BoundVar::new(outer_binder, index)).intern(Interner)) } else { - let ty = ty.super_fold_with(self.as_dyn(), outer_binder)?; - Ok(ty) + ty.try_super_fold_with(self.as_dyn(), outer_binder) } } - fn fold_inference_ty( + fn try_fold_inference_ty( &mut self, _var: InferenceVar, _kind: TyVariableKind, @@ -433,7 +427,7 @@ where } } - fn fold_free_var_ty( + fn try_fold_free_var_ty( &mut self, _bound_var: BoundVar, _outer_binder: DebruijnIndex, @@ -447,7 +441,7 @@ where } } - fn fold_inference_const( + fn try_fold_inference_const( &mut self, ty: Ty, _var: InferenceVar, @@ -460,7 +454,7 @@ where } } - fn fold_free_var_const( + fn try_fold_free_var_const( &mut self, ty: Ty, _bound_var: BoundVar, @@ -473,7 +467,7 @@ where } } - fn fold_inference_lifetime( + fn try_fold_inference_lifetime( &mut self, _var: InferenceVar, _outer_binder: DebruijnIndex, @@ -485,7 +479,7 @@ where } } - fn fold_free_var_lifetime( + fn try_fold_free_var_lifetime( &mut self, _bound_var: BoundVar, _outer_binder: DebruijnIndex, @@ -498,7 +492,7 @@ where } } let mut error_replacer = ErrorReplacer { vars: 0 }; - let value = match t.clone().fold_with(&mut error_replacer, DebruijnIndex::INNERMOST) { + let value = match t.clone().try_fold_with(&mut error_replacer, DebruijnIndex::INNERMOST) { Ok(t) => t, Err(_) => panic!("Encountered unbound or inference vars in {:?}", t), }; diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 532544fee5..223d705b15 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -306,7 +306,7 @@ impl<'a> TyLoweringContext<'a> { // FIXME we're probably doing something wrong here self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16); let ( - parent_params, + _parent_params, self_params, list_params, const_params, @@ -319,7 +319,7 @@ impl<'a> TyLoweringContext<'a> { }; TyKind::BoundVar(BoundVar::new( self.in_binders, - idx as usize + parent_params + self_params + list_params + const_params, + idx as usize + self_params + list_params + const_params, )) .intern(Interner) } @@ -499,14 +499,31 @@ impl<'a> TyLoweringContext<'a> { .intern(Interner) } TypeNs::SelfType(impl_id) => { - let generics = generics(self.db.upcast(), impl_id.into()); - let substs = match self.type_param_mode { - ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db), - ParamLoweringMode::Variable => { - generics.bound_vars_subst(self.db, self.in_binders) + let def = + self.resolver.generic_def().expect("impl should have generic param scope"); + let generics = generics(self.db.upcast(), def); + + match self.type_param_mode { + ParamLoweringMode::Placeholder => { + // `def` can be either impl itself or item within, and we need impl itself + // now. + let generics = generics.parent_generics().unwrap_or(&generics); + let subst = generics.placeholder_subst(self.db); + self.db.impl_self_ty(impl_id).substitute(Interner, &subst) } - }; - self.db.impl_self_ty(impl_id).substitute(Interner, &substs) + ParamLoweringMode::Variable => { + let starting_from = match def { + GenericDefId::ImplId(_) => 0, + // `def` is an item within impl. We need to substitute `BoundVar`s but + // remember that they are for parent (i.e. impl) generic params so they + // come after our own params. + _ => generics.len_self(), + }; + TyBuilder::impl_self_ty(self.db, impl_id) + .fill_with_bound_vars(self.in_binders, starting_from) + .build() + } + } } TypeNs::AdtSelfType(adt) => { let generics = generics(self.db.upcast(), adt.into()); @@ -663,40 +680,31 @@ impl<'a> TyLoweringContext<'a> { fn substs_from_path_segment( &self, segment: PathSegment<'_>, - def_generic: Option, + def: Option, infer_args: bool, explicit_self_ty: Option, ) -> Substitution { + // Remember that the item's own generic args come before its parent's. let mut substs = Vec::new(); - let def_generics = if let Some(def) = def_generic { - generics(self.db.upcast(), def) + let def = if let Some(d) = def { + d } else { return Substitution::empty(Interner); }; + let def_generics = generics(self.db.upcast(), def); let (parent_params, self_params, type_params, const_params, impl_trait_params) = def_generics.provenance_split(); - let total_len = - parent_params + self_params + type_params + const_params + impl_trait_params; + let item_len = self_params + type_params + const_params + impl_trait_params; + let total_len = parent_params + item_len; - let ty_error = GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner); + let ty_error = TyKind::Error.intern(Interner).cast(Interner); let mut def_generic_iter = def_generics.iter_id(); - for _ in 0..parent_params { - if let Some(eid) = def_generic_iter.next() { - match eid { - Either::Left(_) => substs.push(ty_error.clone()), - Either::Right(x) => { - substs.push(unknown_const_as_generic(self.db.const_param_ty(x))) - } - } - } - } - let fill_self_params = || { for x in explicit_self_ty .into_iter() - .map(|x| GenericArgData::Ty(x).intern(Interner)) + .map(|x| x.cast(Interner)) .chain(iter::repeat(ty_error.clone())) .take(self_params) { @@ -757,37 +765,40 @@ impl<'a> TyLoweringContext<'a> { fill_self_params(); } + // These params include those of parent. + let remaining_params: SmallVec<[_; 2]> = def_generic_iter + .map(|eid| match eid { + Either::Left(_) => ty_error.clone(), + Either::Right(x) => unknown_const_as_generic(self.db.const_param_ty(x)), + }) + .collect(); + assert_eq!(remaining_params.len() + substs.len(), total_len); + // handle defaults. In expression or pattern path segments without // explicitly specified type arguments, missing type arguments are inferred // (i.e. defaults aren't used). if !infer_args || had_explicit_args { - if let Some(def_generic) = def_generic { - let defaults = self.db.generic_defaults(def_generic); - assert_eq!(total_len, defaults.len()); + let defaults = self.db.generic_defaults(def); + assert_eq!(total_len, defaults.len()); + let parent_from = item_len - substs.len(); - for default_ty in defaults.iter().skip(substs.len()) { - // each default can depend on the previous parameters - let substs_so_far = Substitution::from_iter(Interner, substs.clone()); - if let Some(_id) = def_generic_iter.next() { - substs.push(default_ty.clone().substitute(Interner, &substs_so_far)); - } - } + for (idx, default_ty) in defaults[substs.len()..item_len].iter().enumerate() { + // each default can depend on the previous parameters + let substs_so_far = Substitution::from_iter( + Interner, + substs.iter().cloned().chain(remaining_params[idx..].iter().cloned()), + ); + substs.push(default_ty.clone().substitute(Interner, &substs_so_far)); } + + // Keep parent's params as unknown. + let mut remaining_params = remaining_params; + substs.extend(remaining_params.drain(parent_from..)); + } else { + substs.extend(remaining_params); } - // add placeholders for args that were not provided - // FIXME: emit diagnostics in contexts where this is not allowed - for eid in def_generic_iter { - match eid { - Either::Left(_) => substs.push(ty_error.clone()), - Either::Right(x) => { - substs.push(unknown_const_as_generic(self.db.const_param_ty(x))) - } - } - } - // If this assert fails, it means you pushed into subst but didn't call .next() of def_generic_iter assert_eq!(substs.len(), total_len); - Substitution::from_iter(Interner, substs) } @@ -981,10 +992,11 @@ impl<'a> TyLoweringContext<'a> { fn lower_dyn_trait(&self, bounds: &[Interned]) -> Ty { let self_ty = TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)).intern(Interner); - // INVARIANT: The principal trait bound must come first. Others may be in any order but - // should be in the same order for the same set but possibly different order of bounds in - // the input. - // This invariant is used by `TyExt::dyn_trait()` and chalk. + // INVARIANT: The principal trait bound, if present, must come first. Others may be in any + // order but should be in the same order for the same set but possibly different order of + // bounds in the input. + // INVARIANT: If this function returns `DynTy`, there should be at least one trait bound. + // These invariants are utilized by `TyExt::dyn_trait()` and chalk. let bounds = self.with_shifted_in(DebruijnIndex::ONE, |ctx| { let mut bounds: Vec<_> = bounds .iter() @@ -1035,6 +1047,12 @@ impl<'a> TyLoweringContext<'a> { return None; } + if bounds.first().and_then(|b| b.trait_id()).is_none() { + // When there's no trait bound, that's an error. This happens when the trait refs + // are unresolved. + return None; + } + // As multiple occurrences of the same auto traits *are* permitted, we dedulicate the // bounds. We shouldn't have repeated elements besides auto traits at this point. bounds.dedup(); @@ -1046,7 +1064,8 @@ impl<'a> TyLoweringContext<'a> { let bounds = crate::make_single_type_binders(bounds); TyKind::Dyn(DynTy { bounds, lifetime: static_lifetime() }).intern(Interner) } else { - // FIXME: report error (additional non-auto traits or associated type rebound) + // FIXME: report error + // (additional non-auto traits, associated type rebound, or no resolved trait) TyKind::Error.intern(Interner) } } @@ -1139,11 +1158,28 @@ fn named_associated_type_shorthand_candidates( }; match res { - TypeNs::SelfType(impl_id) => search( + TypeNs::SelfType(impl_id) => { // we're _in_ the impl -- the binders get added back later. Correct, // but it would be nice to make this more explicit - db.impl_trait(impl_id)?.into_value_and_skipped_binders().0, - ), + let trait_ref = db.impl_trait(impl_id)?.into_value_and_skipped_binders().0; + + let impl_id_as_generic_def: GenericDefId = impl_id.into(); + if impl_id_as_generic_def != def { + // `trait_ref` contains `BoundVar`s bound by impl's `Binders`, but here we need + // `BoundVar`s from `def`'s point of view. + // FIXME: A `HirDatabase` query may be handy if this process is needed in more + // places. It'd be almost identical as `impl_trait_query` where `resolver` would be + // of `def` instead of `impl_id`. + let starting_idx = generics(db.upcast(), def).len_self(); + let subst = TyBuilder::subst_for_def(db, impl_id, None) + .fill_with_bound_vars(DebruijnIndex::INNERMOST, starting_idx) + .build(); + let trait_ref = subst.apply(trait_ref, Interner); + search(trait_ref) + } else { + search(trait_ref) + } + } TypeNs::GenericParam(param_id) => { let predicates = db.generic_predicates_for_param(def, param_id.into(), assoc_name); let res = predicates.iter().find_map(|pred| match pred.skip_binders().skip_binders() { @@ -1160,10 +1196,18 @@ fn named_associated_type_shorthand_candidates( } // Handle `Self::Type` referring to own associated type in trait definitions if let GenericDefId::TraitId(trait_id) = param_id.parent() { - let generics = generics(db.upcast(), trait_id.into()); - if generics.params.type_or_consts[param_id.local_id()].is_trait_self() { + let trait_generics = generics(db.upcast(), trait_id.into()); + if trait_generics.params.type_or_consts[param_id.local_id()].is_trait_self() { + let def_generics = generics(db.upcast(), def); + let starting_idx = match def { + GenericDefId::TraitId(_) => 0, + // `def` is an item within trait. We need to substitute `BoundVar`s but + // remember that they are for parent (i.e. trait) generic params so they + // come after our own params. + _ => def_generics.len_self(), + }; let trait_ref = TyBuilder::trait_ref(db, trait_id) - .fill_with_bound_vars(DebruijnIndex::INNERMOST, 0) + .fill_with_bound_vars(DebruijnIndex::INNERMOST, starting_idx) .build(); return search(trait_ref); } @@ -1405,6 +1449,7 @@ pub(crate) fn generic_defaults_query( let ctx = TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable); let generic_params = generics(db.upcast(), def); + let parent_start_idx = generic_params.len_self(); let defaults = generic_params .iter() @@ -1417,19 +1462,17 @@ pub(crate) fn generic_defaults_query( let val = unknown_const_as_generic( db.const_param_ty(ConstParamId::from_unchecked(id)), ); - return crate::make_binders_with_count(db, idx, &generic_params, val); + return make_binders(db, &generic_params, val); } }; let mut ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |t| ctx.lower_ty(t)); // Each default can only refer to previous parameters. - // type variable default referring to parameter coming - // after it. This is forbidden (FIXME: report - // diagnostic) - ty = fallback_bound_vars(ty, idx); - let val = GenericArgData::Ty(ty).intern(Interner); - crate::make_binders_with_count(db, idx, &generic_params, val) + // Type variable default referring to parameter coming + // after it is forbidden (FIXME: report diagnostic) + ty = fallback_bound_vars(ty, idx, parent_start_idx); + crate::make_binders(db, &generic_params, ty.cast(Interner)) }) .collect(); @@ -1446,15 +1489,14 @@ pub(crate) fn generic_defaults_recover( // we still need one default per parameter let defaults = generic_params .iter_id() - .enumerate() - .map(|(count, id)| { + .map(|id| { let val = match id { itertools::Either::Left(_) => { GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) } itertools::Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)), }; - crate::make_binders_with_count(db, count, &generic_params, val) + crate::make_binders(db, &generic_params, val) }) .collect(); @@ -1633,6 +1675,19 @@ pub enum ValueTyDefId { } impl_from!(FunctionId, StructId, UnionId, EnumVariantId, ConstId, StaticId for ValueTyDefId); +impl ValueTyDefId { + pub(crate) fn to_generic_def_id(self) -> Option { + match self { + Self::FunctionId(id) => Some(id.into()), + Self::StructId(id) => Some(id.into()), + Self::UnionId(id) => Some(id.into()), + Self::EnumVariantId(var) => Some(var.into()), + Self::ConstId(id) => Some(id.into()), + Self::StaticId(_) => None, + } + } +} + /// Build the declared type of an item. This depends on the namespace; e.g. for /// `struct Foo(usize)`, we have two types: The type of the struct itself, and /// the constructor function `(usize) -> Foo` which lives in the values @@ -1816,26 +1871,48 @@ pub(crate) fn const_or_path_to_chalk( } } -/// This replaces any 'free' Bound vars in `s` (i.e. those with indices past -/// num_vars_to_keep) by `TyKind::Unknown`. +/// Replaces any 'free' `BoundVar`s in `s` by `TyKind::Error` from the perspective of generic +/// parameter whose index is `param_index`. A `BoundVar` is free when it is or (syntactically) +/// appears after the generic parameter of `param_index`. fn fallback_bound_vars + HasInterner>( s: T, - num_vars_to_keep: usize, + param_index: usize, + parent_start: usize, ) -> T { + // Keep in mind that parent generic parameters, if any, come *after* those of the item in + // question. In the diagrams below, `c*` and `p*` represent generic parameters of the item and + // its parent respectively. + let is_allowed = |index| { + if param_index < parent_start { + // The parameter of `param_index` is one from the item in question. Any parent generic + // parameters or the item's generic parameters that come before `param_index` is + // allowed. + // [c1, .., cj, .., ck, p1, .., pl] where cj is `param_index` + // ^^^^^^ ^^^^^^^^^^ these are allowed + !(param_index..parent_start).contains(&index) + } else { + // The parameter of `param_index` is one from the parent generics. Only parent generic + // parameters that come before `param_index` are allowed. + // [c1, .., ck, p1, .., pj, .., pl] where pj is `param_index` + // ^^^^^^ these are allowed + (parent_start..param_index).contains(&index) + } + }; + crate::fold_free_vars( s, |bound, binders| { - if bound.index >= num_vars_to_keep && bound.debruijn == DebruijnIndex::INNERMOST { - TyKind::Error.intern(Interner) - } else { + if bound.index_if_innermost().map_or(true, is_allowed) { bound.shifted_in_from(binders).to_ty(Interner) + } else { + TyKind::Error.intern(Interner) } }, |ty, bound, binders| { - if bound.index >= num_vars_to_keep && bound.debruijn == DebruijnIndex::INNERMOST { - unknown_const(ty.clone()) - } else { + if bound.index_if_innermost().map_or(true, is_allowed) { bound.shifted_in_from(binders).to_const(Interner, ty) + } else { + unknown_const(ty.clone()) } }, ) diff --git a/crates/hir-ty/src/mapping.rs b/crates/hir-ty/src/mapping.rs index d765fee0e1..f80fb39c1f 100644 --- a/crates/hir-ty/src/mapping.rs +++ b/crates/hir-ty/src/mapping.rs @@ -103,6 +103,18 @@ impl From for chalk_ir::ClosureId { } } +impl From> for crate::db::InternedGeneratorId { + fn from(id: chalk_ir::GeneratorId) -> Self { + Self::from_intern_id(id.0) + } +} + +impl From for chalk_ir::GeneratorId { + fn from(id: crate::db::InternedGeneratorId) -> Self { + chalk_ir::GeneratorId(id.as_intern_id()) + } +} + pub fn to_foreign_def_id(id: TypeAliasId) -> ForeignDefId { chalk_ir::ForeignDefId(salsa::InternKey::as_intern_id(&id)) } diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 41fcef73d9..a79efeb6da 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -1,7 +1,7 @@ //! This module is concerned with finding methods that a given type provides. //! For details about how this works in rustc, see the method lookup page in the //! [rustc guide](https://rust-lang.github.io/rustc-guide/method-lookup.html) -//! and the corresponding code mostly in librustc_typeck/check/method/probe.rs. +//! and the corresponding code mostly in rustc_hir_analysis/check/method/probe.rs. use std::{iter, ops::ControlFlow, sync::Arc}; use arrayvec::ArrayVec; @@ -654,7 +654,7 @@ fn find_matching_impl( let r = table.run_in_snapshot(|table| { let impl_data = db.impl_data(impl_); let substs = - TyBuilder::subst_for_def(db, impl_).fill_with_inference_vars(table).build(); + TyBuilder::subst_for_def(db, impl_, None).fill_with_inference_vars(table).build(); let impl_ty = db.impl_self_ty(impl_).substitute(Interner, &substs); table @@ -1111,6 +1111,24 @@ pub fn resolve_indexing_op( } None } +/// Returns the receiver type for the try branch trait call. +pub fn resolve_branch_op( + db: &dyn HirDatabase, + env: Arc, + ty: Canonical, + try_trait: TraitId, +) -> Option { + let mut table = InferenceTable::new(db, env.clone()); + let ty = table.instantiate_canonical(ty); + let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty); + for (ty, adj) in deref_chain.into_iter().zip(adj) { + let goal = generic_implements_goal(db, env.clone(), try_trait, &ty); + if db.trait_solve(env.krate, goal.cast(Interner)).is_some() { + return Some(adj); + } + } + None +} macro_rules! check_that { ($cond:expr) => { @@ -1147,10 +1165,9 @@ fn is_valid_candidate( })); if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container { let self_ty_matches = table.run_in_snapshot(|table| { - let subst = - TyBuilder::subst_for_def(db, c).fill_with_inference_vars(table).build(); - let expected_self_ty = - subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner); + let expected_self_ty = TyBuilder::impl_self_ty(db, impl_id) + .fill_with_inference_vars(table) + .build(); table.unify(&expected_self_ty, &self_ty) }); if !self_ty_matches { @@ -1186,31 +1203,26 @@ fn is_valid_fn_candidate( table.run_in_snapshot(|table| { let container = fn_id.lookup(db.upcast()).container; - let impl_subst = match container { + let (impl_subst, expect_self_ty) = match container { ItemContainerId::ImplId(it) => { - TyBuilder::subst_for_def(db, it).fill_with_inference_vars(table).build() + let subst = + TyBuilder::subst_for_def(db, it, None).fill_with_inference_vars(table).build(); + let self_ty = db.impl_self_ty(it).substitute(Interner, &subst); + (subst, self_ty) } ItemContainerId::TraitId(it) => { - TyBuilder::subst_for_def(db, it).fill_with_inference_vars(table).build() + let subst = + TyBuilder::subst_for_def(db, it, None).fill_with_inference_vars(table).build(); + let self_ty = subst.at(Interner, 0).assert_ty_ref(Interner).clone(); + (subst, self_ty) } _ => unreachable!(), }; - let fn_subst = TyBuilder::subst_for_def(db, fn_id) - .use_parent_substs(&impl_subst) + let fn_subst = TyBuilder::subst_for_def(db, fn_id, Some(impl_subst.clone())) .fill_with_inference_vars(table) .build(); - let expect_self_ty = match container { - ItemContainerId::TraitId(_) => fn_subst.at(Interner, 0).assert_ty_ref(Interner).clone(), - ItemContainerId::ImplId(impl_id) => { - fn_subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner) - } - // We should only get called for associated items (impl/trait) - ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { - unreachable!() - } - }; check_that!(table.unify(&expect_self_ty, self_ty)); if let Some(receiver_ty) = receiver_ty { diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index d2f13e4351..ebbc541014 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -16,7 +16,7 @@ use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt}; use expect_test::Expect; use hir_def::{ body::{Body, BodySourceMap, SyntheticSyntax}, - db::DefDatabase, + db::{DefDatabase, InternDatabase}, expr::{ExprId, PatId}, item_scope::ItemScope, nameres::DefMap, @@ -135,6 +135,10 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour let loc = it.lookup(&db); loc.source(&db).value.syntax().text_range().start() } + DefWithBodyId::VariantId(it) => { + let loc = db.lookup_intern_enum(it.parent); + loc.source(&db).value.syntax().text_range().start() + } }); let mut unexpected_type_mismatches = String::new(); for def in defs { @@ -388,6 +392,10 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { let loc = it.lookup(&db); loc.source(&db).value.syntax().text_range().start() } + DefWithBodyId::VariantId(it) => { + let loc = db.lookup_intern_enum(it.parent); + loc.source(&db).value.syntax().text_range().start() + } }); for def in defs { let (_body, source_map) = db.body_with_source_map(def); @@ -453,6 +461,18 @@ fn visit_module( let body = db.body(def); visit_body(db, &body, cb); } + ModuleDefId::AdtId(hir_def::AdtId::EnumId(it)) => { + db.enum_data(it) + .variants + .iter() + .map(|(id, _)| hir_def::EnumVariantId { parent: it, local_id: id }) + .for_each(|it| { + let def = it.into(); + cb(def); + let body = db.body(def); + visit_body(db, &body, cb); + }); + } ModuleDefId::TraitId(it) => { let trait_data = db.trait_data(it); for &(_, item) in trait_data.items.iter() { diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs index bf59fadc2c..d301595bcd 100644 --- a/crates/hir-ty/src/tests/coercion.rs +++ b/crates/hir-ty/src/tests/coercion.rs @@ -294,6 +294,24 @@ fn foo() { ); } +#[test] +fn generator_yield_return_coerce() { + check_no_mismatches( + r#" +fn test() { + let g = || { + yield &1u32; + yield &&1u32; + if true { + return &1u32; + } + &&1u32 + }; +} + "#, + ); +} + #[test] fn assign_coerce() { check_no_mismatches( diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs index 240942e488..8a8ff08cfe 100644 --- a/crates/hir-ty/src/tests/display_source_code.rs +++ b/crates/hir-ty/src/tests/display_source_code.rs @@ -55,6 +55,28 @@ fn main() { ); } +#[test] +fn render_dyn_ty_independent_of_order() { + check_types_source_code( + r#" +auto trait Send {} +trait A { + type Assoc; +} +trait B: A {} + +fn test( + _: &(dyn A + Send), + //^ &(dyn A + Send) + _: &(dyn Send + A), + //^ &(dyn A + Send) + _: &dyn B, + //^ &(dyn B) +) {} + "#, + ); +} + #[test] fn render_dyn_for_ty() { // FIXME diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index eb04bf8778..74de33117e 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -1070,3 +1070,13 @@ fn main() { "#, ); } + +#[test] +fn cfg_params() { + check_types( + r#" +fn my_fn(#[cfg(feature = "feature")] u8: u8, u32: u32) {} + //^^^ u32 +"#, + ); +} diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 23e51a9c16..a155adcec6 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -1488,7 +1488,6 @@ fn regression_11688_4() { #[test] fn gat_crash_1() { - cov_mark::check!(ignore_gats); check_no_mismatches( r#" trait ATrait {} @@ -1527,30 +1526,22 @@ unsafe impl Storage for InlineStorage { #[test] fn gat_crash_3() { - // FIXME: This test currently crashes rust analyzer in a debug build but not in a - // release build (i.e. for the user). With the assumption that tests will always be run - // in debug mode, we catch the unwind and expect that it panicked. See the - // [`crate::utils::generics`] function for more information. - cov_mark::check!(ignore_gats); - std::panic::catch_unwind(|| { - check_no_mismatches( - r#" + check_no_mismatches( + r#" trait Collection { - type Item; - type Member: Collection; - fn add(&mut self, value: Self::Item) -> Result<(), Self::Error>; +type Item; +type Member: Collection; +fn add(&mut self, value: Self::Item) -> Result<(), Self::Error>; } struct ConstGen { - data: [T; N], +data: [T; N], } impl Collection for ConstGen { - type Item = T; - type Member = ConstGen; +type Item = T; +type Member = ConstGen; } - "#, - ); - }) - .expect_err("must panic"); + "#, + ); } #[test] @@ -1691,3 +1682,28 @@ fn macrostmts() -> u8 { "#, ); } + +#[test] +fn dyn_with_unresolved_trait() { + check_types( + r#" +fn foo(a: &dyn DoesNotExist) { + a.bar(); + //^&{unknown} +} + "#, + ); +} + +#[test] +fn self_assoc_with_const_generics_crash() { + check_no_mismatches( + r#" +trait Trait { type Item; } +impl Trait for [T; N] { + type Item = (); + fn f(_: Self::Item) {} +} + "#, + ); +} diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index 4ea103e5d9..080e2ac1b8 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -1693,16 +1693,16 @@ fn infer_type_param() { fn infer_const() { check_infer( r#" - struct Foo; - impl Foo { const ASSOC_CONST: u32 = 0; } - const GLOBAL_CONST: u32 = 101; - fn test() { - const LOCAL_CONST: u32 = 99; - let x = LOCAL_CONST; - let z = GLOBAL_CONST; - let id = Foo::ASSOC_CONST; - } - "#, +struct Foo; +impl Foo { const ASSOC_CONST: u32 = 0; } +const GLOBAL_CONST: u32 = 101; +fn test() { + const LOCAL_CONST: u32 = 99; + let x = LOCAL_CONST; + let z = GLOBAL_CONST; + let id = Foo::ASSOC_CONST; +} +"#, expect![[r#" 48..49 '0': u32 79..82 '101': u32 @@ -1722,17 +1722,17 @@ fn infer_const() { fn infer_static() { check_infer( r#" - static GLOBAL_STATIC: u32 = 101; - static mut GLOBAL_STATIC_MUT: u32 = 101; - fn test() { - static LOCAL_STATIC: u32 = 99; - static mut LOCAL_STATIC_MUT: u32 = 99; - let x = LOCAL_STATIC; - let y = LOCAL_STATIC_MUT; - let z = GLOBAL_STATIC; - let w = GLOBAL_STATIC_MUT; - } - "#, +static GLOBAL_STATIC: u32 = 101; +static mut GLOBAL_STATIC_MUT: u32 = 101; +fn test() { + static LOCAL_STATIC: u32 = 99; + static mut LOCAL_STATIC_MUT: u32 = 99; + let x = LOCAL_STATIC; + let y = LOCAL_STATIC_MUT; + let z = GLOBAL_STATIC; + let w = GLOBAL_STATIC_MUT; +} +"#, expect![[r#" 28..31 '101': u32 69..72 '101': u32 @@ -1751,6 +1751,41 @@ fn infer_static() { ); } +#[test] +fn infer_enum_variant() { + check_infer( + r#" +enum Foo { + A = 15, + B = Foo::A as isize + 1 +} +"#, + expect![[r#" + 19..21 '15': isize + 31..37 'Foo::A': Foo + 31..46 'Foo::A as isize': isize + 31..50 'Foo::A...ze + 1': isize + 49..50 '1': isize + "#]], + ); + check_infer( + r#" +#[repr(u32)] +enum Foo { + A = 15, + B = Foo::A as u32 + 1 +} +"#, + expect![[r#" + 32..34 '15': u32 + 44..50 'Foo::A': Foo + 44..57 'Foo::A as u32': u32 + 44..61 'Foo::A...32 + 1': u32 + 60..61 '1': u32 + "#]], + ); +} + #[test] fn shadowing_primitive() { check_types( @@ -1917,6 +1952,88 @@ fn closure_return_inferred() { ); } +#[test] +fn generator_types_inferred() { + check_infer( + r#" +//- minicore: generator, deref +use core::ops::{Generator, GeneratorState}; +use core::pin::Pin; + +fn f(v: i64) {} +fn test() { + let mut g = |r| { + let a = yield 0; + let a = yield 1; + let a = yield 2; + "return value" + }; + + match Pin::new(&mut g).resume(0usize) { + GeneratorState::Yielded(y) => { f(y); } + GeneratorState::Complete(r) => {} + } +} + "#, + expect![[r#" + 70..71 'v': i64 + 78..80 '{}': () + 91..362 '{ ... } }': () + 101..106 'mut g': |usize| yields i64 -> &str + 109..218 '|r| { ... }': |usize| yields i64 -> &str + 110..111 'r': usize + 113..218 '{ ... }': &str + 127..128 'a': usize + 131..138 'yield 0': usize + 137..138 '0': i64 + 152..153 'a': usize + 156..163 'yield 1': usize + 162..163 '1': i64 + 177..178 'a': usize + 181..188 'yield 2': usize + 187..188 '2': i64 + 198..212 '"return value"': &str + 225..360 'match ... }': () + 231..239 'Pin::new': fn new<&mut |usize| yields i64 -> &str>(&mut |usize| yields i64 -> &str) -> Pin<&mut |usize| yields i64 -> &str> + 231..247 'Pin::n...mut g)': Pin<&mut |usize| yields i64 -> &str> + 231..262 'Pin::n...usize)': GeneratorState + 240..246 '&mut g': &mut |usize| yields i64 -> &str + 245..246 'g': |usize| yields i64 -> &str + 255..261 '0usize': usize + 273..299 'Genera...ded(y)': GeneratorState + 297..298 'y': i64 + 303..312 '{ f(y); }': () + 305..306 'f': fn f(i64) + 305..309 'f(y)': () + 307..308 'y': i64 + 321..348 'Genera...ete(r)': GeneratorState + 346..347 'r': &str + 352..354 '{}': () + "#]], + ); +} + +#[test] +fn generator_resume_yield_return_unit() { + check_no_mismatches( + r#" +//- minicore: generator, deref +use core::ops::{Generator, GeneratorState}; +use core::pin::Pin; +fn test() { + let mut g = || { + let () = yield; + }; + + match Pin::new(&mut g).resume(()) { + GeneratorState::Yielded(()) => {} + GeneratorState::Complete(()) => {} + } +} + "#, + ); +} + #[test] fn fn_pointer_return() { check_infer( diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 21a8631976..b91172e334 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -162,98 +162,16 @@ fn test() { ); } -#[test] -fn infer_try() { - check_types( - r#" -//- /main.rs crate:main deps:core -fn test() { - let r: Result = Result::Ok(1); - let v = r?; - v; -} //^ i32 - -//- /core.rs crate:core -pub mod ops { - pub trait Try { - type Ok; - type Error; - } -} - -pub mod result { - pub enum Result { - Ok(O), - Err(E) - } - - impl crate::ops::Try for Result { - type Ok = O; - type Error = E; - } -} - -pub mod prelude { - pub mod rust_2018 { - pub use crate::{result::*, ops::*}; - } -} -"#, - ); -} - #[test] fn infer_try_trait_v2() { check_types( r#" -//- /main.rs crate:main deps:core -fn test() { - let r: Result = Result::Ok(1); +//- minicore: try +fn test() -> core::ops::ControlFlow { + let r: core::ops::ControlFlow = core::ops::ControlFlow::Continue(1.0); let v = r?; - v; -} //^ i32 - -//- /core.rs crate:core -mod ops { - mod try_trait { - pub trait Try: FromResidual { - type Output; - type Residual; - } - pub trait FromResidual::Residual> {} - } - - pub use self::try_trait::FromResidual; - pub use self::try_trait::Try; -} - -mod convert { - pub trait From {} - impl From for T {} -} - -pub mod result { - use crate::convert::From; - use crate::ops::{Try, FromResidual}; - - pub enum Infallible {} - pub enum Result { - Ok(O), - Err(E) - } - - impl Try for Result { - type Output = O; - type Error = Result; - } - - impl> FromResidual> for Result {} -} - -pub mod prelude { - pub mod rust_2018 { - pub use crate::result::*; - } + //^ f32 + r } "#, ); @@ -279,6 +197,10 @@ fn test() { pub mod iter { pub trait IntoIterator { type Item; + type IntoIter: Iterator; + } + pub trait Iterator { + type Item; } } pub mod prelude { @@ -297,7 +219,13 @@ pub mod collections { } impl IntoIterator for Vec { - type Item=T; + type Item = T; + type IntoIter = IntoIter; + } + + struct IntoIter {} + impl Iterator for IntoIter { + type Item = T; } } "#, diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index 372c3a3cca..c425f35acf 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -13,8 +13,8 @@ use syntax::SmolStr; use crate::{ db::HirDatabase, infer::unify::InferenceTable, AliasEq, AliasTy, Canonical, DomainGoal, Goal, - Guidance, InEnvironment, Interner, ProjectionTy, Solution, TraitRefExt, Ty, TyKind, - WhereClause, + Guidance, InEnvironment, Interner, ProjectionTy, ProjectionTyExt, Solution, TraitRefExt, Ty, + TyKind, WhereClause, }; /// This controls how much 'time' we give the Chalk solver before giving up. @@ -95,7 +95,7 @@ pub(crate) fn trait_solve_query( .. }))) = &goal.value.goal.data(Interner) { - if let TyKind::BoundVar(_) = projection_ty.self_type_parameter(Interner).kind(Interner) { + if let TyKind::BoundVar(_) = projection_ty.self_type_parameter(db).kind(Interner) { // Hack: don't ask Chalk to normalize with an unknown self type, it'll say that's impossible return Some(Solution::Ambig(Guidance::Unknown)); } diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index d6638db028..e54bcb421a 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -4,7 +4,7 @@ use std::iter; use base_db::CrateId; -use chalk_ir::{fold::Shift, BoundVar, DebruijnIndex}; +use chalk_ir::{cast::Cast, fold::Shift, BoundVar, DebruijnIndex}; use hir_def::{ db::DefDatabase, generics::{ @@ -24,8 +24,7 @@ use smallvec::{smallvec, SmallVec}; use syntax::SmolStr; use crate::{ - db::HirDatabase, ChalkTraitId, ConstData, ConstValue, GenericArgData, Interner, Substitution, - TraitRef, TraitRefExt, TyKind, WhereClause, + db::HirDatabase, ChalkTraitId, Interner, Substitution, TraitRef, TraitRefExt, WhereClause, }; pub(crate) fn fn_traits(db: &dyn DefDatabase, krate: CrateId) -> impl Iterator { @@ -174,31 +173,6 @@ pub(super) fn associated_type_by_name_including_super_traits( pub(crate) fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics { let parent_generics = parent_generic_def(db, def).map(|def| Box::new(generics(db, def))); - if parent_generics.is_some() && matches!(def, GenericDefId::TypeAliasId(_)) { - let params = db.generic_params(def); - let parent_params = &parent_generics.as_ref().unwrap().params; - let has_consts = - params.iter().any(|(_, x)| matches!(x, TypeOrConstParamData::ConstParamData(_))); - let parent_has_consts = - parent_params.iter().any(|(_, x)| matches!(x, TypeOrConstParamData::ConstParamData(_))); - return if has_consts || parent_has_consts { - // XXX: treat const generic associated types as not existing to avoid crashes - // (#11769) - // - // Note: Also crashes when the parent has const generics (also even if the GAT - // doesn't use them), see `tests::regression::gat_crash_3` for an example. - // Avoids that by disabling GATs when the parent (i.e. `impl` block) has - // const generics (#12193). - // - // Chalk expects the inner associated type's parameters to come - // *before*, not after the trait's generics as we've always done it. - // Adapting to this requires a larger refactoring - cov_mark::hit!(ignore_gats); - Generics { def, params: Interned::new(Default::default()), parent_generics } - } else { - Generics { def, params, parent_generics } - }; - } Generics { def, params: db.generic_params(def), parent_generics } } @@ -221,23 +195,30 @@ impl Generics { }) } - /// Iterator over types and const params of parent, then self. + /// Iterator over types and const params of self, then parent. pub(crate) fn iter<'a>( &'a self, ) -> impl DoubleEndedIterator + 'a { let to_toc_id = |it: &'a Generics| { move |(local_id, p)| (TypeOrConstParamId { parent: it.def, local_id }, p) }; - self.parent_generics() - .into_iter() - .flat_map(move |it| it.params.iter().map(to_toc_id(it))) - .chain(self.params.iter().map(to_toc_id(self))) + self.params.iter().map(to_toc_id(self)).chain(self.iter_parent()) + } + + /// Iterate over types and const params without parent params. + pub(crate) fn iter_self<'a>( + &'a self, + ) -> impl DoubleEndedIterator + 'a { + let to_toc_id = |it: &'a Generics| { + move |(local_id, p)| (TypeOrConstParamId { parent: it.def, local_id }, p) + }; + self.params.iter().map(to_toc_id(self)) } /// Iterator over types and const params of parent. pub(crate) fn iter_parent<'a>( &'a self, - ) -> impl Iterator + 'a { + ) -> impl DoubleEndedIterator + 'a { self.parent_generics().into_iter().flat_map(|it| { let to_toc_id = move |(local_id, p)| (TypeOrConstParamId { parent: it.def, local_id }, p); @@ -245,12 +226,18 @@ impl Generics { }) } + /// Returns total number of generic parameters in scope, including those from parent. pub(crate) fn len(&self) -> usize { let parent = self.parent_generics().map_or(0, Generics::len); let child = self.params.type_or_consts.len(); parent + child } + /// Returns numbers of generic parameters excluding those from parent. + pub(crate) fn len_self(&self) -> usize { + self.params.type_or_consts.len() + } + /// (parent total, self param, type param list, const param list, impl trait) pub(crate) fn provenance_split(&self) -> (usize, usize, usize, usize, usize) { let ty_iter = || self.params.iter().filter_map(|x| x.1.type_param()); @@ -275,15 +262,17 @@ impl Generics { if param.parent == self.def { let (idx, (_local_id, data)) = self.params.iter().enumerate().find(|(_, (idx, _))| *idx == param.local_id)?; - let parent_len = self.parent_generics().map_or(0, Generics::len); - Some((parent_len + idx, data)) + Some((idx, data)) } else { - self.parent_generics().and_then(|g| g.find_param(param)) + self.parent_generics() + .and_then(|g| g.find_param(param)) + // Remember that parent parameters come after parameters for self. + .map(|(idx, data)| (self.len_self() + idx, data)) } } - fn parent_generics(&self) -> Option<&Generics> { - self.parent_generics.as_ref().map(|it| &**it) + pub(crate) fn parent_generics(&self) -> Option<&Generics> { + self.parent_generics.as_deref() } /// Returns a Substitution that replaces each parameter by a bound variable. @@ -295,18 +284,10 @@ impl Generics { Substitution::from_iter( Interner, self.iter_id().enumerate().map(|(idx, id)| match id { - Either::Left(_) => GenericArgData::Ty( - TyKind::BoundVar(BoundVar::new(debruijn, idx)).intern(Interner), - ) - .intern(Interner), - Either::Right(id) => GenericArgData::Const( - ConstData { - value: ConstValue::BoundVar(BoundVar::new(debruijn, idx)), - ty: db.const_param_ty(id), - } - .intern(Interner), - ) - .intern(Interner), + Either::Left(_) => BoundVar::new(debruijn, idx).to_ty(Interner).cast(Interner), + Either::Right(id) => BoundVar::new(debruijn, idx) + .to_const(Interner, db.const_param_ty(id)) + .cast(Interner), }), ) } @@ -316,18 +297,12 @@ impl Generics { Substitution::from_iter( Interner, self.iter_id().map(|id| match id { - Either::Left(id) => GenericArgData::Ty( - TyKind::Placeholder(crate::to_placeholder_idx(db, id.into())).intern(Interner), - ) - .intern(Interner), - Either::Right(id) => GenericArgData::Const( - ConstData { - value: ConstValue::Placeholder(crate::to_placeholder_idx(db, id.into())), - ty: db.const_param_ty(id), - } - .intern(Interner), - ) - .intern(Interner), + Either::Left(id) => { + crate::to_placeholder_idx(db, id.into()).to_ty(Interner).cast(Interner) + } + Either::Right(id) => crate::to_placeholder_idx(db, id.into()) + .to_const(Interner, db.const_param_ty(id)) + .cast(Interner), }), ) } diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 8e6a2441b3..e1418de3cd 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -13,9 +13,9 @@ doctest = false rustc-hash = "1.1.0" either = "1.7.0" arrayvec = "0.7.2" -itertools = "0.10.3" -smallvec = "1.9.0" -once_cell = "1.12.0" +itertools = "0.10.5" +smallvec = "1.10.0" +once_cell = "1.15.0" stdx = { path = "../stdx", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 5edc16d8bc..6c8b3088ad 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -1,12 +1,12 @@ //! Re-export diagnostics such that clients of `hir` don't have to depend on //! low-level crates. //! -//! This probably isn't the best way to do this -- ideally, diagnistics should +//! This probably isn't the best way to do this -- ideally, diagnostics should //! be expressed in terms of hir types themselves. use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_def::path::ModPath; +use hir_def::{path::ModPath, TraitId}; use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; @@ -33,6 +33,7 @@ diagnostics![ BreakOutsideOfLoop, InactiveCode, IncorrectCase, + IncorrectTryExpr, InvalidDeriveTarget, MacroError, MalformedDerive, @@ -40,6 +41,7 @@ diagnostics![ MissingFields, MissingMatchArms, MissingUnsafe, + NotImplemented, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch, @@ -153,6 +155,16 @@ pub struct MismatchedArgCount { pub expected: usize, pub found: usize, } +#[derive(Debug)] +pub struct IncorrectTryExpr { + pub expr: InFile>, +} +#[derive(Debug)] +pub struct NotImplemented { + pub expr: InFile>, + pub trait_: TraitId, + pub ty: Type, +} #[derive(Debug)] pub struct MissingMatchArms { diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 0e29c52ade..27b2f445d7 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -492,6 +492,9 @@ impl HirDisplay for TypeAlias { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; let data = f.db.type_alias_data(self.id); write!(f, "type {}", data.name)?; + let def_id = GenericDefId::TypeAliasId(self.id); + write_generic_params(def_id, f)?; + write_where_clause(def_id, f)?; if !data.bounds.is_empty() { f.write_str(": ")?; f.write_joined(&data.bounds, " + ")?; diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs index 9c7558d191..f825a72c0f 100644 --- a/crates/hir/src/from_id.rs +++ b/crates/hir/src/from_id.rs @@ -140,6 +140,7 @@ impl From for DefWithBodyId { DefWithBody::Function(it) => DefWithBodyId::FunctionId(it.id), DefWithBody::Static(it) => DefWithBodyId::StaticId(it.id), DefWithBody::Const(it) => DefWithBodyId::ConstId(it.id), + DefWithBody::Variant(it) => DefWithBodyId::VariantId(it.into()), } } } @@ -150,6 +151,7 @@ impl From for DefWithBody { DefWithBodyId::FunctionId(it) => DefWithBody::Function(it.into()), DefWithBodyId::StaticId(it) => DefWithBody::Static(it.into()), DefWithBodyId::ConstId(it) => DefWithBody::Const(it.into()), + DefWithBodyId::VariantId(it) => DefWithBody::Variant(it.into()), } } } @@ -172,9 +174,7 @@ impl From for GenericDefId { GenericDef::Trait(it) => GenericDefId::TraitId(it.id), GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id), GenericDef::Impl(it) => GenericDefId::ImplId(it.id), - GenericDef::Variant(it) => { - GenericDefId::EnumVariantId(EnumVariantId { parent: it.parent.id, local_id: it.id }) - } + GenericDef::Variant(it) => GenericDefId::EnumVariantId(it.into()), GenericDef::Const(it) => GenericDefId::ConstId(it.id), } } @@ -188,9 +188,7 @@ impl From for GenericDef { GenericDefId::TraitId(it) => GenericDef::Trait(it.into()), GenericDefId::TypeAliasId(it) => GenericDef::TypeAlias(it.into()), GenericDefId::ImplId(it) => GenericDef::Impl(it.into()), - GenericDefId::EnumVariantId(it) => { - GenericDef::Variant(Variant { parent: it.parent.into(), id: it.local_id }) - } + GenericDefId::EnumVariantId(it) => GenericDef::Variant(it.into()), GenericDefId::ConstId(it) => GenericDef::Const(it.into()), } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index d2717c5665..e6c5c6b583 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -39,7 +39,7 @@ use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId, ProcMacroKind}; use either::Either; use hir_def::{ - adt::{ReprKind, VariantData}, + adt::{ReprData, VariantData}, body::{BodyDiagnostic, SyntheticSyntax}, expr::{BindingAnnotation, LabelId, Pat, PatId}, generics::{TypeOrConstParamData, TypeParamProvenance}, @@ -50,7 +50,7 @@ use hir_def::{ resolver::{HasResolver, Resolver}, src::HasSource as _, AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, - FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, + EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, }; @@ -61,7 +61,6 @@ use hir_ty::{ diagnostics::BodyValidationDiagnostic, method_resolution::{self, TyFingerprint}, primitive::UintTy, - subst_prefix, traits::FnTrait, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, @@ -73,7 +72,7 @@ use once_cell::unsync::Lazy; use rustc_hash::FxHashSet; use stdx::{impl_from, never}; use syntax::{ - ast::{self, HasAttrs as _, HasDocComments, HasName}, + ast::{self, Expr, HasAttrs as _, HasDocComments, HasName}, AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T, }; @@ -82,11 +81,12 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, - MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, - MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch, - UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, - UnresolvedModule, UnresolvedProcMacro, + AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, IncorrectTryExpr, + InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, + MissingMatchArms, MissingUnsafe, NoSuchField, NotImplemented, + ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, + UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, + UnresolvedProcMacro, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits}, @@ -348,7 +348,10 @@ impl ModuleDef { ModuleDef::Module(it) => it.id.into(), ModuleDef::Const(it) => it.id.into(), ModuleDef::Static(it) => it.id.into(), - _ => return Vec::new(), + ModuleDef::Variant(it) => { + EnumVariantId { parent: it.parent.into(), local_id: it.id }.into() + } + ModuleDef::BuiltinType(_) | ModuleDef::Macro(_) => return Vec::new(), }; let module = match self.module(db) { @@ -377,10 +380,10 @@ impl ModuleDef { ModuleDef::Function(it) => Some(it.into()), ModuleDef::Const(it) => Some(it.into()), ModuleDef::Static(it) => Some(it.into()), + ModuleDef::Variant(it) => Some(it.into()), ModuleDef::Module(_) | ModuleDef::Adt(_) - | ModuleDef::Variant(_) | ModuleDef::Trait(_) | ModuleDef::TypeAlias(_) | ModuleDef::Macro(_) @@ -537,6 +540,30 @@ impl Module { } acc.extend(decl.diagnostics(db)) } + ModuleDef::Adt(adt) => { + match adt { + Adt::Struct(s) => { + for diag in db.struct_data_with_diagnostics(s.id).1.iter() { + emit_def_diagnostic(db, acc, diag); + } + } + Adt::Union(u) => { + for diag in db.union_data_with_diagnostics(u.id).1.iter() { + emit_def_diagnostic(db, acc, diag); + } + } + Adt::Enum(e) => { + for v in e.variants(db) { + acc.extend(ModuleDef::Variant(v).diagnostics(db)); + } + + for diag in db.enum_data_with_diagnostics(e.id).1.iter() { + emit_def_diagnostic(db, acc, diag); + } + } + } + acc.extend(decl.diagnostics(db)) + } _ => acc.extend(decl.diagnostics(db)), } } @@ -874,7 +901,7 @@ impl Struct { Type::from_def(db, self.id) } - pub fn repr(self, db: &dyn HirDatabase) -> Option { + pub fn repr(self, db: &dyn HirDatabase) -> Option { db.struct_data(self.id).repr.clone() } @@ -952,6 +979,21 @@ impl Enum { pub fn ty(self, db: &dyn HirDatabase) -> Type { Type::from_def(db, self.id) } + + /// The type of the enum variant bodies. + pub fn variant_body_ty(self, db: &dyn HirDatabase) -> Type { + Type::new_for_crate( + self.id.lookup(db.upcast()).container.krate(), + TyBuilder::builtin(match db.enum_data(self.id).variant_body_type() { + Either::Left(builtin) => hir_def::builtin_type::BuiltinType::Int(builtin), + Either::Right(builtin) => hir_def::builtin_type::BuiltinType::Uint(builtin), + }), + ) + } + + pub fn is_data_carrying(self, db: &dyn HirDatabase) -> bool { + self.variants(db).iter().any(|v| !matches!(v.kind(db), StructKind::Unit)) + } } impl HasVisibility for Enum { @@ -960,6 +1002,12 @@ impl HasVisibility for Enum { } } +impl From<&Variant> for DefWithBodyId { + fn from(&v: &Variant) -> Self { + DefWithBodyId::VariantId(v.into()) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Variant { pub(crate) parent: Enum, @@ -994,6 +1042,14 @@ impl Variant { pub(crate) fn variant_data(self, db: &dyn HirDatabase) -> Arc { db.enum_data(self.parent.id).variants[self.id].variant_data.clone() } + + pub fn value(self, db: &dyn HirDatabase) -> Option { + self.source(db)?.value.expr() + } + + pub fn eval(self, db: &dyn HirDatabase) -> Result { + db.const_eval_variant(self.into()) + } } /// Variants inherit visibility from the parent enum. @@ -1034,7 +1090,7 @@ impl Adt { pub fn ty_with_args(self, db: &dyn HirDatabase, args: &[Type]) -> Type { let id = AdtId::from(self); let mut it = args.iter().map(|t| t.ty.clone()); - let ty = TyBuilder::def_ty(db, id.into()) + let ty = TyBuilder::def_ty(db, id.into(), None) .fill(|x| { let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); match x { @@ -1129,8 +1185,9 @@ pub enum DefWithBody { Function(Function), Static(Static), Const(Const), + Variant(Variant), } -impl_from!(Function, Const, Static for DefWithBody); +impl_from!(Function, Const, Static, Variant for DefWithBody); impl DefWithBody { pub fn module(self, db: &dyn HirDatabase) -> Module { @@ -1138,6 +1195,7 @@ impl DefWithBody { DefWithBody::Const(c) => c.module(db), DefWithBody::Function(f) => f.module(db), DefWithBody::Static(s) => s.module(db), + DefWithBody::Variant(v) => v.module(db), } } @@ -1146,6 +1204,7 @@ impl DefWithBody { DefWithBody::Function(f) => Some(f.name(db)), DefWithBody::Static(s) => Some(s.name(db)), DefWithBody::Const(c) => c.name(db), + DefWithBody::Variant(v) => Some(v.name(db)), } } @@ -1155,6 +1214,7 @@ impl DefWithBody { DefWithBody::Function(it) => it.ret_type(db), DefWithBody::Static(it) => it.ty(db), DefWithBody::Const(it) => it.ty(db), + DefWithBody::Variant(it) => it.parent.variant_body_ty(db), } } @@ -1163,6 +1223,7 @@ impl DefWithBody { DefWithBody::Function(it) => it.id.into(), DefWithBody::Static(it) => it.id.into(), DefWithBody::Const(it) => it.id.into(), + DefWithBody::Variant(it) => it.into(), } } @@ -1222,30 +1283,45 @@ impl DefWithBody { let infer = db.infer(self.into()); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); for d in &infer.diagnostics { - match d { + match *d { hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(*expr); + let field = source_map.field_syntax(expr); acc.push(NoSuchField { field }.into()) } - &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { + hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { let expr = source_map .expr_syntax(expr) .expect("break outside of loop in synthetic syntax"); acc.push(BreakOutsideOfLoop { expr, is_break }.into()) } hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - match source_map.expr_syntax(*call_expr) { + match source_map.expr_syntax(call_expr) { Ok(source_ptr) => acc.push( MismatchedArgCount { call_expr: source_ptr, - expected: *expected, - found: *found, + expected: expected, + found: found, } .into(), ), Err(SyntheticSyntax) => (), } } + hir_ty::InferenceDiagnostic::IncorrectTryTarget { expr } => { + let expr = source_map.expr_syntax(expr).expect("try in synthetic syntax"); + acc.push(IncorrectTryExpr { expr }.into()) + } + hir_ty::InferenceDiagnostic::DoesNotImplement { expr, trait_, ref ty } => { + let expr = source_map.expr_syntax(expr).expect("try in synthetic syntax"); + acc.push( + NotImplemented { + expr, + trait_, + ty: Type::new(db, DefWithBodyId::from(self), ty.clone()), + } + .into(), + ) + } } } for (expr, mismatch) in infer.expr_type_mismatches() { @@ -1379,6 +1455,7 @@ impl DefWithBody { DefWithBody::Function(it) => it.into(), DefWithBody::Static(it) => it.into(), DefWithBody::Const(it) => it.into(), + DefWithBody::Variant(it) => it.into(), }; for diag in hir_ty::diagnostics::incorrect_case(db, krate, def.into()) { acc.push(diag.into()) @@ -2485,7 +2562,7 @@ impl TypeParam { let resolver = self.id.parent().resolver(db.upcast()); let ty = params.get(local_idx)?.clone(); let subst = TyBuilder::placeholder_subst(db, self.id.parent()); - let ty = ty.substitute(Interner, &subst_prefix(&subst, local_idx)); + let ty = ty.substitute(Interner, &subst); match ty.data(Interner) { GenericArgData::Ty(x) => Some(Type::new_with_resolver_inner(db, &resolver, x.clone())), _ => None, @@ -2739,7 +2816,22 @@ impl Type { } fn from_def(db: &dyn HirDatabase, def: impl HasResolver + Into) -> Type { - let ty = TyBuilder::def_ty(db, def.into()).fill_with_unknown().build(); + let ty_def = def.into(); + let parent_subst = match ty_def { + TyDefId::TypeAliasId(id) => match id.lookup(db.upcast()).container { + ItemContainerId::TraitId(id) => { + let subst = TyBuilder::subst_for_def(db, id, None).fill_with_unknown().build(); + Some(subst) + } + ItemContainerId::ImplId(id) => { + let subst = TyBuilder::subst_for_def(db, id, None).fill_with_unknown().build(); + Some(subst) + } + _ => None, + }, + _ => None, + }; + let ty = TyBuilder::def_ty(db, ty_def, parent_subst).fill_with_unknown().build(); Type::new(db, def, ty) } @@ -2879,7 +2971,11 @@ impl Type { alias: TypeAlias, ) -> Option { let mut args = args.iter(); - let projection = TyBuilder::assoc_type_projection(db, alias.id) + let trait_id = match alias.id.lookup(db.upcast()).container { + ItemContainerId::TraitId(id) => id, + _ => unreachable!("non assoc type alias reached in normalize_trait_assoc_type()"), + }; + let parent_subst = TyBuilder::subst_for_def(db, trait_id, None) .push(self.ty.clone()) .fill(|x| { // FIXME: this code is not covered in tests. @@ -2891,6 +2987,8 @@ impl Type { } }) .build(); + // FIXME: We don't handle GATs yet. + let projection = TyBuilder::assoc_type_projection(db, alias.id, Some(parent_subst)).build(); let ty = db.normalize_projection(projection, self.env.clone()); if ty.is_unknown() { @@ -2940,7 +3038,7 @@ impl Type { let adt = adt_id.into(); match adt { - Adt::Struct(s) => matches!(s.repr(db), Some(ReprKind::Packed)), + Adt::Struct(s) => matches!(s.repr(db), Some(ReprData { packed: true, .. })), _ => false, } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 416b6f5806..119ec3210e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -257,6 +257,11 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { pub fn original_ast_node(&self, node: N) -> Option { self.imp.original_ast_node(node) } + /// Attempts to map the node out of macro expanded files. + /// This only work for attribute expansions, as other ones do not have nodes as input. + pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option { + self.imp.original_syntax_node(node) + } pub fn diagnostics_display_range(&self, diagnostics: InFile) -> FileRange { self.imp.diagnostics_display_range(diagnostics) @@ -956,6 +961,16 @@ impl<'db> SemanticsImpl<'db> { ) } + fn original_syntax_node(&self, node: &SyntaxNode) -> Option { + let InFile { file_id, .. } = self.find_file(node); + InFile::new(file_id, node).original_syntax_node(self.db.upcast()).map( + |InFile { file_id, value }| { + self.cache(find_root(&value), file_id); + value + }, + ) + } + fn diagnostics_display_range(&self, src: InFile) -> FileRange { let root = self.parse_or_expand(src.file_id).unwrap(); let node = src.map(|it| it.to_node(&root)); diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index ba9a1cfb6b..fa45e3c12e 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -115,7 +115,7 @@ pub(super) struct SourceToDefCtx<'a, 'b> { } impl SourceToDefCtx<'_, '_> { - pub(super) fn file_to_def(&mut self, file: FileId) -> SmallVec<[ModuleId; 1]> { + pub(super) fn file_to_def(&self, file: FileId) -> SmallVec<[ModuleId; 1]> { let _p = profile::span("SourceBinder::to_module_def"); let mut mods = SmallVec::new(); for &crate_id in self.db.relevant_crates(file).iter() { @@ -130,7 +130,7 @@ impl SourceToDefCtx<'_, '_> { mods } - pub(super) fn module_to_def(&mut self, src: InFile) -> Option { + pub(super) fn module_to_def(&self, src: InFile) -> Option { let _p = profile::span("module_to_def"); let parent_declaration = src .syntax() @@ -151,7 +151,7 @@ impl SourceToDefCtx<'_, '_> { Some(def_map.module_id(child_id)) } - pub(super) fn source_file_to_def(&mut self, src: InFile) -> Option { + pub(super) fn source_file_to_def(&self, src: InFile) -> Option { let _p = profile::span("source_file_to_def"); let file_id = src.file_id.original_file(self.db.upcast()); self.file_to_def(file_id).get(0).copied() @@ -384,7 +384,7 @@ impl SourceToDefCtx<'_, '_> { } else { let it = ast::Variant::cast(container.value)?; let def = self.enum_variant_to_def(InFile::new(container.file_id, it))?; - VariantId::from(def).into() + DefWithBodyId::from(def).into() }; Some(cont) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 342912b678..07bae2b38c 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -22,7 +22,7 @@ use hir_def::{ resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, type_ref::Mutability, AsMacroCall, AssocItemId, DefWithBodyId, FieldId, FunctionId, ItemContainerId, LocalFieldId, - Lookup, ModuleDefId, VariantId, + Lookup, ModuleDefId, TraitId, VariantId, }; use hir_expand::{ builtin_fn_macro::BuiltinFnLikeExpander, @@ -302,10 +302,15 @@ impl SourceAnalyzer { } } + let future_trait = db + .lang_item(self.resolver.krate(), hir_expand::name![future_trait].to_smol_str())? + .as_trait()?; let poll_fn = db .lang_item(self.resolver.krate(), hir_expand::name![poll].to_smol_str())? .as_function()?; - let substs = hir_ty::TyBuilder::subst_for_def(db, poll_fn).push(ty.clone()).build(); + // HACK: subst for `poll()` coincides with that for `Future` because `poll()` itself + // doesn't have any generic parameters, so we skip building another subst for `poll()`. + let substs = hir_ty::TyBuilder::subst_for_def(db, future_trait, None).push(ty).build(); Some(self.resolve_impl_method_or_trait_def(db, poll_fn, &substs)) } @@ -321,8 +326,10 @@ impl SourceAnalyzer { }; let ty = self.ty_of_expr(db, &prefix_expr.expr()?.into())?; - let op_fn = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?; - let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn).push(ty.clone()).build(); + let (op_trait, op_fn) = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?; + // HACK: subst for all methods coincides with that for their trait because the methods + // don't have any generic parameters, so we skip building another subst for the methods. + let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None).push(ty.clone()).build(); Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs)) } @@ -337,8 +344,10 @@ impl SourceAnalyzer { let lang_item_name = name![index]; - let op_fn = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?; - let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn) + let (op_trait, op_fn) = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?; + // HACK: subst for all methods coincides with that for their trait because the methods + // don't have any generic parameters, so we skip building another subst for the methods. + let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None) .push(base_ty.clone()) .push(index_ty.clone()) .build(); @@ -354,10 +363,14 @@ impl SourceAnalyzer { let lhs = self.ty_of_expr(db, &binop_expr.lhs()?.into())?; let rhs = self.ty_of_expr(db, &binop_expr.rhs()?.into())?; - let op_fn = lang_names_for_bin_op(op) + let (op_trait, op_fn) = lang_names_for_bin_op(op) .and_then(|(name, lang_item)| self.lang_trait_fn(db, &lang_item, &name))?; - let substs = - hir_ty::TyBuilder::subst_for_def(db, op_fn).push(lhs.clone()).push(rhs.clone()).build(); + // HACK: subst for `index()` coincides with that for `Index` because `index()` itself + // doesn't have any generic parameters, so we skip building another subst for `index()`. + let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None) + .push(lhs.clone()) + .push(rhs.clone()) + .build(); Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs)) } @@ -371,7 +384,13 @@ impl SourceAnalyzer { let op_fn = db.lang_item(self.resolver.krate(), name![branch].to_smol_str())?.as_function()?; - let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn).push(ty.clone()).build(); + let op_trait = match op_fn.lookup(db.upcast()).container { + ItemContainerId::TraitId(id) => id, + _ => return None, + }; + // HACK: subst for `branch()` coincides with that for `Try` because `branch()` itself + // doesn't have any generic parameters, so we skip building another subst for `branch()`. + let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None).push(ty.clone()).build(); Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs)) } @@ -799,9 +818,10 @@ impl SourceAnalyzer { db: &dyn HirDatabase, lang_trait: &Name, method_name: &Name, - ) -> Option { - db.trait_data(db.lang_item(self.resolver.krate(), lang_trait.to_smol_str())?.as_trait()?) - .method_by_name(method_name) + ) -> Option<(TraitId, FunctionId)> { + let trait_id = db.lang_item(self.resolver.krate(), lang_trait.to_smol_str())?.as_trait()?; + let fn_id = db.trait_data(trait_id).method_by_name(method_name)?; + Some((trait_id, fn_id)) } fn ty_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<&Ty> { diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index 616a406c72..fd78decda4 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -244,6 +244,10 @@ impl<'a> SymbolCollector<'a> { DefWithBodyId::ConstId(id) => Some( id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(), ), + DefWithBodyId::VariantId(id) => Some({ + let db = self.db.upcast(); + id.parent.lookup(db).source(db).value.name()?.text().into() + }), } } diff --git a/crates/ide-assists/Cargo.toml b/crates/ide-assists/Cargo.toml index fca09d384c..57a41f3d9a 100644 --- a/crates/ide-assists/Cargo.toml +++ b/crates/ide-assists/Cargo.toml @@ -12,7 +12,7 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" -itertools = "0.10.3" +itertools = "0.10.5" either = "1.7.0" stdx = { path = "../stdx", version = "0.0.0" } diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index e257218ba9..678dc877d1 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs @@ -156,6 +156,8 @@ pub(super) fn find_importable_node( { ImportAssets::for_method_call(&method_under_caret, &ctx.sema) .zip(Some(method_under_caret.syntax().clone().into())) + } else if let Some(_) = ctx.find_node_at_offset_with_descend::() { + None } else if let Some(pat) = ctx .find_node_at_offset_with_descend::() .filter(ast::IdentPat::is_simple_ident) @@ -268,6 +270,20 @@ mod tests { assert_eq!(labels, order); } + #[test] + fn ignore_parameter_name() { + check_assist_not_applicable( + auto_import, + r" + mod foo { + pub mod bar {} + } + + fn foo(bar$0: &str) {} + ", + ); + } + #[test] fn prefer_shorter_paths() { let before = r" diff --git a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs new file mode 100644 index 0000000000..8d11e0bac9 --- /dev/null +++ b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -0,0 +1,822 @@ +use either::Either; +use ide_db::defs::Definition; +use itertools::Itertools; +use syntax::{ + ast::{self, AstNode, HasGenericParams, HasVisibility}, + match_ast, SyntaxKind, SyntaxNode, +}; + +use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists}; + +// Assist: convert_named_struct_to_tuple_struct +// +// Converts struct with named fields to tuple struct, and analogously for enum variants with named +// fields. +// +// ``` +// struct Point$0 { x: f32, y: f32 } +// +// impl Point { +// pub fn new(x: f32, y: f32) -> Self { +// Point { x, y } +// } +// +// pub fn x(&self) -> f32 { +// self.x +// } +// +// pub fn y(&self) -> f32 { +// self.y +// } +// } +// ``` +// -> +// ``` +// struct Point(f32, f32); +// +// impl Point { +// pub fn new(x: f32, y: f32) -> Self { +// Point(x, y) +// } +// +// pub fn x(&self) -> f32 { +// self.0 +// } +// +// pub fn y(&self) -> f32 { +// self.1 +// } +// } +// ``` +pub(crate) fn convert_named_struct_to_tuple_struct( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let strukt = ctx + .find_node_at_offset::() + .map(Either::Left) + .or_else(|| ctx.find_node_at_offset::().map(Either::Right))?; + let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; + let record_fields = match field_list { + ast::FieldList::RecordFieldList(it) => it, + ast::FieldList::TupleFieldList(_) => return None, + }; + let strukt_def = match &strukt { + Either::Left(s) => Either::Left(ctx.sema.to_def(s)?), + Either::Right(v) => Either::Right(ctx.sema.to_def(v)?), + }; + let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range(); + + acc.add( + AssistId("convert_named_struct_to_tuple_struct", AssistKind::RefactorRewrite), + "Convert to tuple struct", + target, + |edit| { + edit_field_references(ctx, edit, record_fields.fields()); + edit_struct_references(ctx, edit, strukt_def); + edit_struct_def(ctx, edit, &strukt, record_fields); + }, + ) +} + +fn edit_struct_def( + ctx: &AssistContext<'_>, + edit: &mut SourceChangeBuilder, + strukt: &Either, + record_fields: ast::RecordFieldList, +) { + let tuple_fields = record_fields + .fields() + .filter_map(|f| Some(ast::make::tuple_field(f.visibility(), f.ty()?))); + let tuple_fields = ast::make::tuple_field_list(tuple_fields); + let record_fields_text_range = record_fields.syntax().text_range(); + + edit.edit_file(ctx.file_id()); + edit.replace(record_fields_text_range, tuple_fields.syntax().text()); + + if let Either::Left(strukt) = strukt { + if let Some(w) = strukt.where_clause() { + let mut where_clause = w.to_string(); + if where_clause.ends_with(',') { + where_clause.pop(); + } + where_clause.push(';'); + + edit.delete(w.syntax().text_range()); + edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text()); + edit.insert(record_fields_text_range.end(), where_clause); + edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text()); + + if let Some(tok) = strukt + .generic_param_list() + .and_then(|l| l.r_angle_token()) + .and_then(|tok| tok.next_token()) + .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE) + { + edit.delete(tok.text_range()); + } + } else { + edit.insert(record_fields_text_range.end(), ";"); + } + } + + if let Some(tok) = record_fields + .l_curly_token() + .and_then(|tok| tok.prev_token()) + .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE) + { + edit.delete(tok.text_range()) + } +} + +fn edit_struct_references( + ctx: &AssistContext<'_>, + edit: &mut SourceChangeBuilder, + strukt: Either, +) { + let strukt_def = match strukt { + Either::Left(s) => Definition::Adt(hir::Adt::Struct(s)), + Either::Right(v) => Definition::Variant(v), + }; + let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); + + let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> { + match_ast! { + match node { + ast::RecordPat(record_struct_pat) => { + edit.replace( + record_struct_pat.syntax().text_range(), + ast::make::tuple_struct_pat( + record_struct_pat.path()?, + record_struct_pat + .record_pat_field_list()? + .fields() + .filter_map(|pat| pat.pat()) + ) + .to_string() + ); + }, + ast::RecordExpr(record_expr) => { + let path = record_expr.path()?; + let args = record_expr + .record_expr_field_list()? + .fields() + .filter_map(|f| f.expr()) + .join(", "); + + edit.replace(record_expr.syntax().text_range(), format!("{path}({args})")); + }, + _ => return None, + } + } + Some(()) + }; + + for (file_id, refs) in usages { + edit.edit_file(file_id); + for r in refs { + for node in r.name.syntax().ancestors() { + if edit_node(edit, node).is_some() { + break; + } + } + } + } +} + +fn edit_field_references( + ctx: &AssistContext<'_>, + edit: &mut SourceChangeBuilder, + fields: impl Iterator, +) { + for (index, field) in fields.enumerate() { + let field = match ctx.sema.to_def(&field) { + Some(it) => it, + None => continue, + }; + let def = Definition::Field(field); + let usages = def.usages(&ctx.sema).all(); + for (file_id, refs) in usages { + edit.edit_file(file_id); + for r in refs { + if let Some(name_ref) = r.name.as_name_ref() { + // Only edit the field reference if it's part of a `.field` access + if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() { + edit.replace(name_ref.syntax().text_range(), index.to_string()); + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn not_applicable_other_than_record_struct() { + check_assist_not_applicable(convert_named_struct_to_tuple_struct, r#"struct Foo$0(u32)"#); + check_assist_not_applicable(convert_named_struct_to_tuple_struct, r#"struct Foo$0;"#); + } + + #[test] + fn convert_simple_struct() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner; +struct A$0 { inner: Inner } + +impl A { + fn new(inner: Inner) -> A { + A { inner } + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.inner + } +}"#, + r#" +struct Inner; +struct A(Inner); + +impl A { + fn new(inner: Inner) -> A { + A(inner) + } + + fn new_with_default() -> A { + A::new(Inner) + } + + fn into_inner(self) -> Inner { + self.0 + } +}"#, + ); + } + + #[test] + fn convert_struct_referenced_via_self_kw() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner; +struct A$0 { inner: Inner } + +impl A { + fn new(inner: Inner) -> Self { + Self { inner } + } + + fn new_with_default() -> Self { + Self::new(Inner) + } + + fn into_inner(self) -> Inner { + self.inner + } +}"#, + r#" +struct Inner; +struct A(Inner); + +impl A { + fn new(inner: Inner) -> Self { + Self(inner) + } + + fn new_with_default() -> Self { + Self::new(Inner) + } + + fn into_inner(self) -> Inner { + self.0 + } +}"#, + ); + } + + #[test] + fn convert_destructured_struct() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner; +struct A$0 { inner: Inner } + +impl A { + fn into_inner(self) -> Inner { + let A { inner: a } = self; + a + } + + fn into_inner_via_self(self) -> Inner { + let Self { inner } = self; + inner + } +}"#, + r#" +struct Inner; +struct A(Inner); + +impl A { + fn into_inner(self) -> Inner { + let A(a) = self; + a + } + + fn into_inner_via_self(self) -> Inner { + let Self(inner) = self; + inner + } +}"#, + ); + } + + #[test] + fn convert_struct_with_visibility() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct A$0 { + pub first: u32, + pub(crate) second: u64 +} + +impl A { + fn new() -> A { + A { first: 42, second: 42 } + } + + fn into_first(self) -> u32 { + self.first + } + + fn into_second(self) -> u64 { + self.second + } +}"#, + r#" +struct A(pub u32, pub(crate) u64); + +impl A { + fn new() -> A { + A(42, 42) + } + + fn into_first(self) -> u32 { + self.0 + } + + fn into_second(self) -> u64 { + self.1 + } +}"#, + ); + } + + #[test] + fn convert_struct_with_wrapped_references() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner$0 { uint: u32 } +struct Outer { inner: Inner } + +impl Outer { + fn new() -> Self { + Self { inner: Inner { uint: 42 } } + } + + fn into_inner(self) -> u32 { + self.inner.uint + } + + fn into_inner_destructed(self) -> u32 { + let Outer { inner: Inner { uint: x } } = self; + x + } +}"#, + r#" +struct Inner(u32); +struct Outer { inner: Inner } + +impl Outer { + fn new() -> Self { + Self { inner: Inner(42) } + } + + fn into_inner(self) -> u32 { + self.inner.0 + } + + fn into_inner_destructed(self) -> u32 { + let Outer { inner: Inner(x) } = self; + x + } +}"#, + ); + + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Inner { uint: u32 } +struct Outer$0 { inner: Inner } + +impl Outer { + fn new() -> Self { + Self { inner: Inner { uint: 42 } } + } + + fn into_inner(self) -> u32 { + self.inner.uint + } + + fn into_inner_destructed(self) -> u32 { + let Outer { inner: Inner { uint: x } } = self; + x + } +}"#, + r#" +struct Inner { uint: u32 } +struct Outer(Inner); + +impl Outer { + fn new() -> Self { + Self(Inner { uint: 42 }) + } + + fn into_inner(self) -> u32 { + self.0.uint + } + + fn into_inner_destructed(self) -> u32 { + let Outer(Inner { uint: x }) = self; + x + } +}"#, + ); + } + + #[test] + fn convert_struct_with_multi_file_references() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +//- /main.rs +struct Inner; +struct A$0 { inner: Inner } + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A { inner: Inner }; +} +"#, + r#" +//- /main.rs +struct Inner; +struct A(Inner); + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A(Inner); +} +"#, + ); + } + + #[test] + fn convert_struct_with_where_clause() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct Wrap$0 +where + T: Display, +{ field1: T } +"#, + r#" +struct Wrap(T) +where + T: Display; + +"#, + ); + } + + #[test] + fn not_applicable_other_than_record_variant() { + check_assist_not_applicable( + convert_named_struct_to_tuple_struct, + r#"enum Enum { Variant$0(usize) };"#, + ); + check_assist_not_applicable( + convert_named_struct_to_tuple_struct, + r#"enum Enum { Variant$0 }"#, + ); + } + + #[test] + fn convert_simple_variant() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +enum A { + $0Variant { field1: usize }, +} + +impl A { + fn new(value: usize) -> A { + A::Variant { field1: value } + } + + fn new_with_default() -> A { + A::new(Default::default()) + } + + fn value(self) -> usize { + match self { + A::Variant { field1: value } => value, + } + } +}"#, + r#" +enum A { + Variant(usize), +} + +impl A { + fn new(value: usize) -> A { + A::Variant(value) + } + + fn new_with_default() -> A { + A::new(Default::default()) + } + + fn value(self) -> usize { + match self { + A::Variant(value) => value, + } + } +}"#, + ); + } + + #[test] + fn convert_variant_referenced_via_self_kw() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +enum A { + $0Variant { field1: usize }, +} + +impl A { + fn new(value: usize) -> A { + Self::Variant { field1: value } + } + + fn new_with_default() -> A { + Self::new(Default::default()) + } + + fn value(self) -> usize { + match self { + Self::Variant { field1: value } => value, + } + } +}"#, + r#" +enum A { + Variant(usize), +} + +impl A { + fn new(value: usize) -> A { + Self::Variant(value) + } + + fn new_with_default() -> A { + Self::new(Default::default()) + } + + fn value(self) -> usize { + match self { + Self::Variant(value) => value, + } + } +}"#, + ); + } + + #[test] + fn convert_destructured_variant() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +enum A { + $0Variant { field1: usize }, +} + +impl A { + fn into_inner(self) -> usize { + let A::Variant { field1: first } = self; + first + } + + fn into_inner_via_self(self) -> usize { + let Self::Variant { field1: first } = self; + first + } +}"#, + r#" +enum A { + Variant(usize), +} + +impl A { + fn into_inner(self) -> usize { + let A::Variant(first) = self; + first + } + + fn into_inner_via_self(self) -> usize { + let Self::Variant(first) = self; + first + } +}"#, + ); + } + + #[test] + fn convert_variant_with_wrapped_references() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +enum Inner { + $0Variant { field1: usize }, +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant { field1: 42 }) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant { field1: x }) = self; + x + } +}"#, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant(42)) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant(x)) = self; + x + } +}"#, + ); + + check_assist( + convert_named_struct_to_tuple_struct, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + $0Variant { field1: Inner }, +} + +impl Outer { + fn new() -> Self { + Self::Variant { field1: Inner::Variant(42) } + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant { field1: Inner::Variant(x) } = self; + x + } +}"#, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant(42)) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant(x)) = self; + x + } +}"#, + ); + } + + #[test] + fn convert_variant_with_multi_file_references() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +//- /main.rs +struct Inner; +enum A { + $0Variant { field1: Inner }, +} + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A::Variant { field1: Inner }; +} +"#, + r#" +//- /main.rs +struct Inner; +enum A { + Variant(Inner), +} + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A::Variant(Inner); +} +"#, + ); + } + + #[test] + fn convert_directly_used_variant() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +//- /main.rs +struct Inner; +enum A { + $0Variant { field1: Inner }, +} + +mod foo; + +//- /foo.rs +use crate::{A::Variant, Inner}; +fn f() { + let a = Variant { field1: Inner }; +} +"#, + r#" +//- /main.rs +struct Inner; +enum A { + Variant(Inner), +} + +mod foo; + +//- /foo.rs +use crate::{A::Variant, Inner}; +fn f() { + let a = Variant(Inner); +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index 8d5cab283d..970e948dfd 100644 --- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -9,7 +9,7 @@ use ide_db::{ search::FileReference, FxHashSet, RootDatabase, }; -use itertools::{Itertools, Position}; +use itertools::Itertools; use syntax::{ ast::{ self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasAttrs, HasGenericParams, @@ -298,37 +298,7 @@ fn update_variant(variant: &ast::Variant, generics: Option 0) - .map(|generics| { - let mut generic_str = String::with_capacity(8); - - for (p, more) in generics.generic_params().with_position().map(|p| match p { - Position::First(p) | Position::Middle(p) => (p, true), - Position::Last(p) | Position::Only(p) => (p, false), - }) { - match p { - ast::GenericParam::ConstParam(konst) => { - if let Some(name) = konst.name() { - generic_str.push_str(name.text().as_str()); - } - } - ast::GenericParam::LifetimeParam(lt) => { - if let Some(lt) = lt.lifetime() { - generic_str.push_str(lt.text().as_str()); - } - } - ast::GenericParam::TypeParam(ty) => { - if let Some(name) = ty.name() { - generic_str.push_str(name.text().as_str()); - } - } - } - if more { - generic_str.push_str(", "); - } - } - - make::ty(&format!("{}<{}>", &name.text(), &generic_str)) - }) + .map(|generics| make::ty(&format!("{}{}", &name.text(), generics.to_generic_args()))) .unwrap_or_else(|| make::ty(&name.text())); // change from a record to a tuple field list diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index e26c76da18..8b67982f91 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -1,4 +1,4 @@ -use hir::{HasSource, HirDisplay, Module, Semantics, TypeInfo}; +use hir::{Adt, HasSource, HirDisplay, Module, Semantics, TypeInfo}; use ide_db::{ base_db::FileId, defs::{Definition, NameRefClass}, @@ -145,7 +145,8 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { return None; } let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?; - let (target, insert_offset) = get_method_target(ctx, &target_module, &impl_)?; + let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; + let function_builder = FunctionBuilder::from_method_call(ctx, &call, &fn_name, target_module, target)?; let text_range = call.syntax().text_range(); @@ -174,10 +175,11 @@ fn add_func_to_accumulator( label: String, ) -> Option<()> { acc.add(AssistId("generate_function", AssistKind::Generate), label, text_range, |builder| { - let function_template = function_builder.render(); + let indent = IndentLevel::from_node(function_builder.target.syntax()); + let function_template = function_builder.render(adt_name.is_some()); let mut func = function_template.to_string(ctx.config.snippet_cap); if let Some(name) = adt_name { - func = format!("\nimpl {} {{\n{}\n}}", name, func); + func = format!("\n{}impl {} {{\n{}\n{}}}", indent, name, func, indent); } builder.edit_file(file); match ctx.config.snippet_cap { @@ -307,7 +309,7 @@ impl FunctionBuilder { }) } - fn render(self) -> FunctionTemplate { + fn render(self, is_method: bool) -> FunctionTemplate { let placeholder_expr = make::ext::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 }; @@ -325,8 +327,14 @@ impl FunctionBuilder { match self.target { GeneratedFunctionTarget::BehindItem(it) => { - let indent = IndentLevel::from_node(&it); - leading_ws = format!("\n\n{}", indent); + let mut indent = IndentLevel::from_node(&it); + if is_method { + indent = indent + 1; + leading_ws = format!("{}", indent); + } else { + leading_ws = format!("\n\n{}", indent); + } + fn_def = fn_def.indent(indent); trailing_ws = String::new(); } @@ -411,14 +419,13 @@ fn get_fn_target( fn get_method_target( ctx: &AssistContext<'_>, - target_module: &Module, impl_: &Option, + adt: &Adt, ) -> Option<(GeneratedFunctionTarget, TextSize)> { let target = match impl_ { Some(impl_) => next_space_for_fn_in_impl(impl_)?, None => { - next_space_for_fn_in_module(ctx.sema.db, &target_module.definition_source(ctx.sema.db))? - .1 + GeneratedFunctionTarget::BehindItem(adt.source(ctx.sema.db)?.syntax().value.clone()) } }; Some((target.clone(), get_insert_offset(&target))) @@ -437,7 +444,7 @@ fn assoc_fn_target_info( return None; } let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?; - let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?; + let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; Some(TargetInfo::new(target_module, adt_name, target, file, insert_offset)) } @@ -1468,14 +1475,12 @@ fn foo() {S.bar$0();} ", r" struct S; -fn foo() {S.bar();} impl S { - - -fn bar(&self) ${0:-> _} { - todo!() -} + fn bar(&self) ${0:-> _} { + todo!() + } } +fn foo() {S.bar();} ", ) } @@ -1516,14 +1521,12 @@ fn foo() {s::S.bar$0();} r" mod s { pub struct S; -impl S { - - - pub(crate) fn bar(&self) ${0:-> _} { - todo!() + impl S { + pub(crate) fn bar(&self) ${0:-> _} { + todo!() + } } } -} fn foo() {s::S.bar();} ", ) @@ -1544,18 +1547,16 @@ mod s { ", r" struct S; +impl S { + fn bar(&self) ${0:-> _} { + todo!() + } +} mod s { fn foo() { super::S.bar(); } } -impl S { - - -fn bar(&self) ${0:-> _} { - todo!() -} -} ", ) @@ -1571,14 +1572,12 @@ fn foo() {$0S.bar();} ", r" struct S; -fn foo() {S.bar();} impl S { - - -fn bar(&self) ${0:-> _} { - todo!() -} + fn bar(&self) ${0:-> _} { + todo!() + } } +fn foo() {S.bar();} ", ) } @@ -1593,14 +1592,12 @@ fn foo() {S::bar$0();} ", r" struct S; -fn foo() {S::bar();} impl S { - - -fn bar() ${0:-> _} { - todo!() -} + fn bar() ${0:-> _} { + todo!() + } } +fn foo() {S::bar();} ", ) } @@ -1641,14 +1638,12 @@ fn foo() {s::S::bar$0();} r" mod s { pub struct S; -impl S { - - - pub(crate) fn bar() ${0:-> _} { - todo!() + impl S { + pub(crate) fn bar() ${0:-> _} { + todo!() + } } } -} fn foo() {s::S::bar();} ", ) @@ -1664,14 +1659,12 @@ fn foo() {$0S::bar();} ", r" struct S; -fn foo() {S::bar();} impl S { - - -fn bar() ${0:-> _} { - todo!() -} + fn bar() ${0:-> _} { + todo!() + } } +fn foo() {S::bar();} ", ) } @@ -1841,16 +1834,14 @@ fn main() { ", r" enum Foo {} +impl Foo { + fn new() ${0:-> _} { + todo!() + } +} fn main() { Foo::new(); } -impl Foo { - - -fn new() ${0:-> _} { - todo!() -} -} ", ) } diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs index 68287a20bf..307cea3d0a 100644 --- a/crates/ide-assists/src/handlers/generate_impl.rs +++ b/crates/ide-assists/src/handlers/generate_impl.rs @@ -52,6 +52,7 @@ mod tests { use super::*; + // FIXME: break up into separate test fns #[test] fn test_add_impl() { check_assist( @@ -134,6 +135,18 @@ mod tests { }"#, ); + check_assist( + generate_impl, + r#" + struct Defaulted {}$0"#, + r#" + struct Defaulted {} + + impl Defaulted { + $0 + }"#, + ); + check_assist( generate_impl, r#"pub trait Trait {} diff --git a/crates/ide-assists/src/handlers/move_format_string_arg.rs b/crates/ide-assists/src/handlers/move_format_string_arg.rs index 92b2fa79d7..aa710d2ce6 100644 --- a/crates/ide-assists/src/handlers/move_format_string_arg.rs +++ b/crates/ide-assists/src/handlers/move_format_string_arg.rs @@ -7,6 +7,7 @@ use ide_db::{ }, }; use itertools::Itertools; +use stdx::format_to; use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange}; // Assist: move_format_string_arg @@ -78,20 +79,26 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) // Extract existing arguments in macro let tokens = - tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec(); + tt.token_trees_and_tokens().collect_vec(); let mut existing_args: Vec = vec![]; let mut current_arg = String::new(); - if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] = + if let [_opening_bracket, NodeOrToken::Token(format_string), _args_start_comma, tokens @ .., NodeOrToken::Token(end_bracket)] = tokens.as_slice() { for t in tokens { - if t.kind() == COMMA { - existing_args.push(current_arg.trim().into()); - current_arg.clear(); - } else { - current_arg.push_str(t.text()); + match t { + NodeOrToken::Node(n) => { + format_to!(current_arg, "{n}"); + }, + NodeOrToken::Token(t) if t.kind() == COMMA=> { + existing_args.push(current_arg.trim().into()); + current_arg.clear(); + }, + NodeOrToken::Token(t) => { + current_arg.push_str(t.text()); + }, } } existing_args.push(current_arg.trim().into()); @@ -261,6 +268,27 @@ fn main() { fn main() { print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } +"#, + ), + ); + } + + #[test] + fn nested_tt() { + check_assist( + move_format_string_arg, + &add_macro_decl( + r#" +fn main() { + print!("My name is {} {x$0 + x}", stringify!(Paperino)) +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("My name is {} {}"$0, stringify!(Paperino), x + x) +} "#, ), ); diff --git a/crates/ide-assists/src/handlers/unwrap_tuple.rs b/crates/ide-assists/src/handlers/unwrap_tuple.rs new file mode 100644 index 0000000000..25c58d086e --- /dev/null +++ b/crates/ide-assists/src/handlers/unwrap_tuple.rs @@ -0,0 +1,159 @@ +use syntax::{ + ast::{self, edit::AstNodeEdit}, + AstNode, T, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: unwrap_tuple +// +// Unwrap the tuple to different variables. +// +// ``` +// # //- minicore: result +// fn main() { +// $0let (foo, bar) = ("Foo", "Bar"); +// } +// ``` +// -> +// ``` +// fn main() { +// let foo = "Foo"; +// let bar = "Bar"; +// } +// ``` +pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let let_kw = ctx.find_token_syntax_at_offset(T![let])?; + let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?; + let indent_level = let_stmt.indent_level().0 as usize; + let pat = let_stmt.pat()?; + let ty = let_stmt.ty(); + let init = let_stmt.initializer()?; + + // This only applies for tuple patterns, types, and initializers. + let tuple_pat = match pat { + ast::Pat::TuplePat(pat) => pat, + _ => return None, + }; + let tuple_ty = ty.and_then(|it| match it { + ast::Type::TupleType(ty) => Some(ty), + _ => None, + }); + let tuple_init = match init { + ast::Expr::TupleExpr(expr) => expr, + _ => return None, + }; + + if tuple_pat.fields().count() != tuple_init.fields().count() { + return None; + } + if let Some(tys) = &tuple_ty { + if tuple_pat.fields().count() != tys.fields().count() { + return None; + } + } + + let parent = let_kw.parent()?; + + acc.add( + AssistId("unwrap_tuple", AssistKind::RefactorRewrite), + "Unwrap tuple", + let_kw.text_range(), + |edit| { + let indents = " ".repeat(indent_level); + + // If there is an ascribed type, insert that type for each declaration, + // otherwise, omit that type. + if let Some(tys) = tuple_ty { + let mut zipped_decls = String::new(); + for (pat, ty, expr) in + itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields()) + { + zipped_decls.push_str(&format!("{}let {pat}: {ty} = {expr};\n", indents)) + } + edit.replace(parent.text_range(), zipped_decls.trim()); + } else { + let mut zipped_decls = String::new(); + for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) { + zipped_decls.push_str(&format!("{}let {pat} = {expr};\n", indents)); + } + edit.replace(parent.text_range(), zipped_decls.trim()); + } + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn unwrap_tuples() { + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar) = ("Foo", "Bar"); +} +"#, + r#" +fn main() { + let foo = "Foo"; + let bar = "Bar"; +} +"#, + ); + + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar, baz) = ("Foo", "Bar", "Baz"); +} +"#, + r#" +fn main() { + let foo = "Foo"; + let bar = "Bar"; + let baz = "Baz"; +} +"#, + ); + } + + #[test] + fn unwrap_tuple_with_types() { + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar): (u8, i32) = (5, 10); +} +"#, + r#" +fn main() { + let foo: u8 = 5; + let bar: i32 = 10; +} +"#, + ); + + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar, baz): (u8, i32, f64) = (5, 10, 17.5); +} +"#, + r#" +fn main() { + let foo: u8 = 5; + let bar: i32 = 10; + let baz: f64 = 17.5; +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 812d22efbd..a07318cefa 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -121,6 +121,7 @@ mod handlers { mod convert_iter_for_each_to_for; mod convert_let_else_to_match; mod convert_tuple_struct_to_named_struct; + mod convert_named_struct_to_tuple_struct; mod convert_to_guarded_return; mod convert_two_arm_bool_match_to_matches_macro; mod convert_while_to_loop; @@ -189,6 +190,7 @@ mod handlers { mod replace_turbofish_with_explicit_type; mod split_import; mod unmerge_match_arm; + mod unwrap_tuple; mod sort_items; mod toggle_ignore; mod unmerge_use; @@ -217,6 +219,7 @@ mod handlers { convert_iter_for_each_to_for::convert_iter_for_each_to_for, convert_iter_for_each_to_for::convert_for_loop_with_for_each, convert_let_else_to_match::convert_let_else_to_match, + convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct, convert_to_guarded_return::convert_to_guarded_return, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro, @@ -291,6 +294,7 @@ mod handlers { unnecessary_async::unnecessary_async, unwrap_block::unwrap_block, unwrap_result_return_type::unwrap_result_return_type, + unwrap_tuple::unwrap_tuple, wrap_return_type_in_result::wrap_return_type_in_result, // These are manually sorted for better priorities. By default, // priority is determined by the size of the target range (smaller diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 258144bae3..f7f2417d07 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -96,8 +96,10 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { }); let actual = { - let source_change = - assist.source_change.expect("Assist did not contain any source changes"); + let source_change = assist + .source_change + .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty()) + .expect("Assist did not contain any source changes"); let mut actual = before; if let Some(source_file_edit) = source_change.get_source_edit(file_id) { source_file_edit.apply(&mut actual); @@ -140,8 +142,10 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_la match (assist, expected) { (Some(assist), ExpectedResult::After(after)) => { - let source_change = - assist.source_change.expect("Assist did not contain any source changes"); + let source_change = assist + .source_change + .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty()) + .expect("Assist did not contain any source changes"); let skip_header = source_change.source_file_edits.len() == 1 && source_change.file_system_edits.len() == 0; @@ -228,6 +232,7 @@ fn assist_order_field_struct() { assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); + assert_eq!(assists.next().expect("expected assist").label, "Convert to tuple struct"); assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 3a696635af..2c4000efe0 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -407,6 +407,47 @@ fn main() { ) } +#[test] +fn doctest_convert_named_struct_to_tuple_struct() { + check_doc_test( + "convert_named_struct_to_tuple_struct", + r#####" +struct Point$0 { x: f32, y: f32 } + +impl Point { + pub fn new(x: f32, y: f32) -> Self { + Point { x, y } + } + + pub fn x(&self) -> f32 { + self.x + } + + pub fn y(&self) -> f32 { + self.y + } +} +"#####, + r#####" +struct Point(f32, f32); + +impl Point { + pub fn new(x: f32, y: f32) -> Self { + Point(x, y) + } + + pub fn x(&self) -> f32 { + self.0 + } + + pub fn y(&self) -> f32 { + self.1 + } +} +"#####, + ) +} + #[test] fn doctest_convert_to_guarded_return() { check_doc_test( @@ -2386,6 +2427,25 @@ fn foo() -> i32 { 42i32 } ) } +#[test] +fn doctest_unwrap_tuple() { + check_doc_test( + "unwrap_tuple", + r#####" +//- minicore: result +fn main() { + $0let (foo, bar) = ("Foo", "Bar"); +} +"#####, + r#####" +fn main() { + let foo = "Foo"; + let bar = "Bar"; +} +"#####, + ) +} + #[test] fn doctest_wrap_return_type_in_result() { check_doc_test( diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 4ab6e2627f..38396cd7d7 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -2,8 +2,6 @@ use std::ops; -use itertools::Itertools; - pub(crate) use gen_trait_fn_body::gen_trait_fn_body; use hir::{db::HirDatabase, HirDisplay, Semantics}; use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap}; @@ -15,7 +13,7 @@ use syntax::{ edit_in_place::{AttrsOwnerEdit, Removable}, make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, }, - ted, AstNode, AstToken, Direction, SmolStr, SourceFile, + ted, AstNode, AstToken, Direction, SourceFile, SyntaxKind::*, SyntaxNode, TextRange, TextSize, T, }; @@ -424,34 +422,44 @@ pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: & } fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String { - let generic_params = adt.generic_param_list(); + // Ensure lifetime params are before type & const params + let generic_params = adt.generic_param_list().map(|generic_params| { + let lifetime_params = + generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); + let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { + // remove defaults since they can't be specified in impls + match param { + ast::TypeOrConstParam::Type(param) => { + let param = param.clone_for_update(); + param.remove_default(); + Some(ast::GenericParam::TypeParam(param)) + } + ast::TypeOrConstParam::Const(param) => { + let param = param.clone_for_update(); + param.remove_default(); + Some(ast::GenericParam::ConstParam(param)) + } + } + }); + + make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) + }); + + // FIXME: use syntax::make & mutable AST apis instead + // `trait_text` and `code` can't be opaque blobs of text let mut buf = String::with_capacity(code.len()); + + // Copy any cfg attrs from the original adt buf.push_str("\n\n"); - adt.attrs() - .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)) - .for_each(|attr| buf.push_str(format!("{}\n", attr).as_str())); + let cfg_attrs = adt + .attrs() + .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)); + cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n"))); + + // `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}` buf.push_str("impl"); if let Some(generic_params) = &generic_params { - let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax())); - let toc_params = generic_params.type_or_const_params().map(|toc_param| { - let type_param = match toc_param { - ast::TypeOrConstParam::Type(x) => x, - ast::TypeOrConstParam::Const(x) => return x.syntax().to_string(), - }; - let mut buf = String::new(); - if let Some(it) = type_param.name() { - format_to!(buf, "{}", it.syntax()); - } - if let Some(it) = type_param.colon_token() { - format_to!(buf, "{} ", it); - } - if let Some(it) = type_param.type_bound_list() { - format_to!(buf, "{}", it.syntax()); - } - buf - }); - let generics = lifetimes.chain(toc_params).format(", "); - format_to!(buf, "<{}>", generics); + format_to!(buf, "{generic_params}"); } buf.push(' '); if let Some(trait_text) = trait_text { @@ -460,23 +468,15 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str } buf.push_str(&adt.name().unwrap().text()); if let Some(generic_params) = generic_params { - let lifetime_params = generic_params - .lifetime_params() - .filter_map(|it| it.lifetime()) - .map(|it| SmolStr::from(it.text())); - let toc_params = generic_params - .type_or_const_params() - .filter_map(|it| it.name()) - .map(|it| SmolStr::from(it.text())); - format_to!(buf, "<{}>", lifetime_params.chain(toc_params).format(", ")) + format_to!(buf, "{}", generic_params.to_generic_args()); } match adt.where_clause() { Some(where_clause) => { - format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code); + format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}"); } None => { - format_to!(buf, " {{\n{}\n}}", code); + format_to!(buf, " {{\n{code}\n}}"); } } diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml index 8c9d6b2286..75835bce95 100644 --- a/crates/ide-completion/Cargo.toml +++ b/crates/ide-completion/Cargo.toml @@ -11,10 +11,10 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" -itertools = "0.10.3" +itertools = "0.10.5" -once_cell = "1.12.0" -smallvec = "1.9.0" +once_cell = "1.15.0" +smallvec = "1.10.0" stdx = { path = "../stdx", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 97b90c62dd..296dfc1425 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -19,6 +19,7 @@ pub(crate) mod snippet; pub(crate) mod r#type; pub(crate) mod use_; pub(crate) mod vis; +pub(crate) mod env_vars; use std::iter; diff --git a/crates/ide-completion/src/completions/env_vars.rs b/crates/ide-completion/src/completions/env_vars.rs new file mode 100644 index 0000000000..09e95e53de --- /dev/null +++ b/crates/ide-completion/src/completions/env_vars.rs @@ -0,0 +1,150 @@ +//! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html) +use hir::Semantics; +use ide_db::{syntax_helpers::node_ext::macro_call_for_string_token, RootDatabase}; +use syntax::ast::{self, IsString}; + +use crate::{ + completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind, +}; + +const CARGO_DEFINED_VARS: &[(&str, &str)] = &[ + ("CARGO","Path to the cargo binary performing the build"), + ("CARGO_MANIFEST_DIR","The directory containing the manifest of your package"), + ("CARGO_PKG_VERSION","The full version of your package"), + ("CARGO_PKG_VERSION_MAJOR","The major version of your package"), + ("CARGO_PKG_VERSION_MINOR","The minor version of your package"), + ("CARGO_PKG_VERSION_PATCH","The patch version of your package"), + ("CARGO_PKG_VERSION_PRE","The pre-release version of your package"), + ("CARGO_PKG_AUTHORS","Colon separated list of authors from the manifest of your package"), + ("CARGO_PKG_NAME","The name of your package"), + ("CARGO_PKG_DESCRIPTION","The description from the manifest of your package"), + ("CARGO_PKG_HOMEPAGE","The home page from the manifest of your package"), + ("CARGO_PKG_REPOSITORY","The repository from the manifest of your package"), + ("CARGO_PKG_LICENSE","The license from the manifest of your package"), + ("CARGO_PKG_LICENSE_FILE","The license file from the manifest of your package"), + ("CARGO_PKG_RUST_VERSION","The Rust version from the manifest of your package. Note that this is the minimum Rust version supported by the package, not the current Rust version"), + ("CARGO_CRATE_NAME","The name of the crate that is currently being compiled"), + ("CARGO_BIN_NAME","The name of the binary that is currently being compiled (if it is a binary). This name does not include any file extension, such as .exe"), + ("CARGO_PRIMARY_PACKAGE","This environment variable will be set if the package being built is primary. Primary packages are the ones the user selected on the command-line, either with -p flags or the defaults based on the current directory and the default workspace members. This environment variable will not be set when building dependencies. This is only set when compiling the package (not when running binaries or tests)"), + ("CARGO_TARGET_TMPDIR","Only set when building integration test or benchmark code. This is a path to a directory inside the target directory where integration tests or benchmarks are free to put any data needed by the tests/benches. Cargo initially creates this directory but doesn't manage its content in any way, this is the responsibility of the test code") +]; + +pub(crate) fn complete_cargo_env_vars( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + expanded: &ast::String, +) -> Option<()> { + guard_env_macro(expanded, &ctx.sema)?; + let range = expanded.text_range_between_quotes()?; + + CARGO_DEFINED_VARS.iter().for_each(|(var, detail)| { + let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var); + item.detail(*detail); + item.add_to(acc); + }); + + Some(()) +} + +fn guard_env_macro(string: &ast::String, semantics: &Semantics<'_, RootDatabase>) -> Option<()> { + let call = macro_call_for_string_token(string)?; + let name = call.path()?.segment()?.name_ref()?; + let makro = semantics.resolve_macro_call(&call)?; + let db = semantics.db; + + match name.text().as_str() { + "env" | "option_env" if makro.kind(db) == hir::MacroKind::BuiltIn => Some(()), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_edit, completion_list}; + + fn check(macro_name: &str) { + check_edit( + "CARGO_BIN_NAME", + &format!( + r#" + #[rustc_builtin_macro] + macro_rules! {} {{ + ($var:literal) => {{ 0 }} + }} + + fn main() {{ + let foo = {}!("CAR$0"); + }} + "#, + macro_name, macro_name + ), + &format!( + r#" + #[rustc_builtin_macro] + macro_rules! {} {{ + ($var:literal) => {{ 0 }} + }} + + fn main() {{ + let foo = {}!("CARGO_BIN_NAME"); + }} + "#, + macro_name, macro_name + ), + ); + } + #[test] + fn completes_env_variable_in_env() { + check("env") + } + + #[test] + fn completes_env_variable_in_option_env() { + check("option_env"); + } + + #[test] + fn doesnt_complete_in_random_strings() { + let fixture = r#" + fn main() { + let foo = "CA$0"; + } + "#; + + let completions = completion_list(fixture); + assert!(completions.is_empty(), "Completions weren't empty: {}", completions); + } + + #[test] + fn doesnt_complete_in_random_macro() { + let fixture = r#" + macro_rules! bar { + ($($arg:tt)*) => { 0 } + } + + fn main() { + let foo = bar!("CA$0"); + + } + "#; + + let completions = completion_list(fixture); + assert!(completions.is_empty(), "Completions weren't empty: {}", completions); + } + + #[test] + fn doesnt_complete_for_shadowed_macro() { + let fixture = r#" + macro_rules! env { + ($var:literal) => { 0 } + } + + fn main() { + let foo = env!("CA$0"); + } + "#; + + let completions = completion_list(fixture); + assert!(completions.is_empty(), "Completions weren't empty: {}", completions) + } +} diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index 785db6fde1..e82cbfdcb8 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -38,7 +38,7 @@ use ide_db::{ }; use syntax::{ ast::{self, edit_in_place::AttrsOwnerEdit}, - AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T, + AstNode, SyntaxElement, SyntaxKind, TextRange, T, }; use text_edit::TextEdit; @@ -85,20 +85,36 @@ fn complete_trait_impl_name( name: &Option, kind: ImplCompletionKind, ) -> Option<()> { - let token = ctx.token.clone(); let item = match name { Some(name) => name.syntax().parent(), - None => if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token } - .parent(), + None => { + let token = &ctx.token; + match token.kind() { + SyntaxKind::WHITESPACE => token.prev_token()?, + _ => token.clone(), + } + .parent() + } }?; - complete_trait_impl( - acc, - ctx, - kind, - replacement_range(ctx, &item), - // item -> ASSOC_ITEM_LIST -> IMPL - &ast::Impl::cast(item.parent()?.parent()?)?, - ); + let item = ctx.sema.original_syntax_node(&item)?; + // item -> ASSOC_ITEM_LIST -> IMPL + let impl_def = ast::Impl::cast(item.parent()?.parent()?)?; + let replacement_range = { + // ctx.sema.original_ast_node(item)?; + let first_child = item + .children_with_tokens() + .find(|child| { + !matches!( + child.kind(), + SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR + ) + }) + .unwrap_or_else(|| SyntaxElement::Node(item.clone())); + + TextRange::new(first_child.text_range().start(), ctx.source_range().end()) + }; + + complete_trait_impl(acc, ctx, kind, replacement_range, &impl_def); Some(()) } @@ -341,17 +357,6 @@ fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String { syntax.trim_end().to_owned() } -fn replacement_range(ctx: &CompletionContext<'_>, item: &SyntaxNode) -> TextRange { - let first_child = item - .children_with_tokens() - .find(|child| { - !matches!(child.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR) - }) - .unwrap_or_else(|| SyntaxElement::Node(item.clone())); - - TextRange::new(first_child.text_range().start(), ctx.source_range().end()) -} - #[cfg(test)] mod tests { use expect_test::{expect, Expect}; diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index a5e854b74d..9850813a0c 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -1,4 +1,4 @@ -//! See `CompletionContext` structure. +//! See [`CompletionContext`] structure. mod analysis; #[cfg(test)] @@ -23,7 +23,10 @@ use syntax::{ }; use text_edit::Indel; -use crate::CompletionConfig; +use crate::{ + context::analysis::{expand_and_analyze, AnalysisResult}, + CompletionConfig, +}; const COMPLETION_MARKER: &str = "intellijRulezz"; @@ -561,15 +564,27 @@ impl<'a> CompletionContext<'a> { let edit = Indel::insert(offset, COMPLETION_MARKER.to_string()); parse.reparse(&edit).tree() }; - let fake_ident_token = - file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?; + // always pick the token to the immediate left of the cursor, as that is what we are actually + // completing on let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; - let token = sema.descend_into_macros_single(original_token.clone()); + + let AnalysisResult { + analysis, + expected: (expected_type, expected_name), + qualifier_ctx, + token, + offset, + } = expand_and_analyze( + &sema, + original_file.syntax().clone(), + file_with_fake_ident.syntax().clone(), + offset, + &original_token, + )?; // adjust for macro input, this still fails if there is no token written yet - let scope_offset = if original_token == token { offset } else { token.text_range().end() }; - let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?; + let scope = sema.scope_at_offset(&token.parent()?, offset)?; let krate = scope.krate(); let module = scope.module(); @@ -583,7 +598,7 @@ impl<'a> CompletionContext<'a> { let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count(); - let mut ctx = CompletionContext { + let ctx = CompletionContext { sema, scope, db, @@ -593,19 +608,13 @@ impl<'a> CompletionContext<'a> { token, krate, module, - expected_name: None, - expected_type: None, - qualifier_ctx: Default::default(), + expected_name, + expected_type, + qualifier_ctx, locals, depth_from_crate_root, }; - let ident_ctx = ctx.expand_and_analyze( - original_file.syntax().clone(), - file_with_fake_ident.syntax().clone(), - offset, - fake_ident_token, - )?; - Some((ctx, ident_ctx)) + Some((ctx, analysis)) } } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 01dd9a234f..04111ec7ef 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -11,1063 +11,1094 @@ use syntax::{ }; use crate::context::{ - AttrCtx, CompletionAnalysis, CompletionContext, DotAccess, DotAccessKind, ExprCtx, - ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, NameRefContext, - NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathKind, PatternContext, - PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, TypeLocation, - COMPLETION_MARKER, + AttrCtx, CompletionAnalysis, DotAccess, DotAccessKind, ExprCtx, ItemListKind, LifetimeContext, + LifetimeKind, NameContext, NameKind, NameRefContext, NameRefKind, ParamContext, ParamKind, + PathCompletionCtx, PathKind, PatternContext, PatternRefutability, Qualified, QualifierCtx, + TypeAscriptionTarget, TypeLocation, COMPLETION_MARKER, }; -impl<'a> CompletionContext<'a> { - /// Expand attributes and macro calls at the current cursor position for both the original file - /// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original - /// and speculative states stay in sync. - pub(super) fn expand_and_analyze( - &mut self, - mut original_file: SyntaxNode, - mut speculative_file: SyntaxNode, - mut offset: TextSize, - mut fake_ident_token: SyntaxToken, - ) -> Option { - let _p = profile::span("CompletionContext::expand_and_fill"); - let mut derive_ctx = None; +struct ExpansionResult { + original_file: SyntaxNode, + speculative_file: SyntaxNode, + offset: TextSize, + fake_ident_token: SyntaxToken, + derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>, +} - 'expansion: loop { - let parent_item = - |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast); - let ancestor_items = iter::successors( - Option::zip( - find_node_at_offset::(&original_file, offset), - find_node_at_offset::(&speculative_file, offset), +pub(super) struct AnalysisResult { + pub(super) analysis: CompletionAnalysis, + pub(super) expected: (Option, Option), + pub(super) qualifier_ctx: QualifierCtx, + pub(super) token: SyntaxToken, + pub(super) offset: TextSize, +} + +pub(super) fn expand_and_analyze( + sema: &Semantics<'_, RootDatabase>, + original_file: SyntaxNode, + speculative_file: SyntaxNode, + offset: TextSize, + original_token: &SyntaxToken, +) -> Option { + // as we insert after the offset, right biased will *always* pick the identifier no matter + // if there is an ident already typed or not + let fake_ident_token = speculative_file.token_at_offset(offset).right_biased()?; + // the relative offset between the cursor and the *identifier* token we are completing on + let relative_offset = offset - fake_ident_token.text_range().start(); + // make the offset point to the start of the original token, as that is what the + // intermediate offsets calculated in expansion always points to + let offset = offset - relative_offset; + let expansion = expand(sema, original_file, speculative_file, offset, fake_ident_token); + // add the relative offset back, so that left_biased finds the proper token + let offset = expansion.offset + relative_offset; + let token = expansion.original_file.token_at_offset(offset).left_biased()?; + + analyze(sema, expansion, original_token, &token).map(|(analysis, expected, qualifier_ctx)| { + AnalysisResult { analysis, expected, qualifier_ctx, token, offset } + }) +} + +/// Expand attributes and macro calls at the current cursor position for both the original file +/// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original +/// and speculative states stay in sync. +fn expand( + sema: &Semantics<'_, RootDatabase>, + mut original_file: SyntaxNode, + mut speculative_file: SyntaxNode, + mut offset: TextSize, + mut fake_ident_token: SyntaxToken, +) -> ExpansionResult { + let _p = profile::span("CompletionContext::expand"); + let mut derive_ctx = None; + + 'expansion: loop { + let parent_item = + |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast); + let ancestor_items = iter::successors( + Option::zip( + find_node_at_offset::(&original_file, offset), + find_node_at_offset::(&speculative_file, offset), + ), + |(a, b)| parent_item(a).zip(parent_item(b)), + ); + + // first try to expand attributes as these are always the outermost macro calls + 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items { + match ( + sema.expand_attr_macro(&actual_item), + sema.speculative_expand_attr_macro( + &actual_item, + &item_with_fake_ident, + fake_ident_token.clone(), ), - |(a, b)| parent_item(a).zip(parent_item(b)), - ); - - // first try to expand attributes as these are always the outermost macro calls - 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items { - match ( - self.sema.expand_attr_macro(&actual_item), - self.sema.speculative_expand_attr_macro( - &actual_item, - &item_with_fake_ident, - fake_ident_token.clone(), - ), - ) { - // maybe parent items have attributes, so continue walking the ancestors - (None, None) => continue 'ancestors, - // successful expansions - (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { - let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { - // offset outside of bounds from the original expansion, - // stop here to prevent problems from happening - break 'expansion; - } - original_file = actual_expansion; - speculative_file = fake_expansion; - fake_ident_token = fake_mapped_token; - offset = new_offset; - continue 'expansion; - } - // exactly one expansion failed, inconsistent state so stop expanding completely - _ => break 'expansion, - } - } - - // No attributes have been expanded, so look for macro_call! token trees or derive token trees - let orig_tt = match find_node_at_offset::(&original_file, offset) { - Some(it) => it, - None => break 'expansion, - }; - let spec_tt = match find_node_at_offset::(&speculative_file, offset) { - Some(it) => it, - None => break 'expansion, - }; - - // Expand pseudo-derive expansion - if let (Some(orig_attr), Some(spec_attr)) = ( - orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), - spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), ) { - if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = ( - self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr), - self.sema.speculative_expand_derive_as_pseudo_attr_macro( - &orig_attr, - &spec_attr, - fake_ident_token.clone(), - ), - ) { - derive_ctx = Some(( - actual_expansion, - fake_expansion, - fake_mapped_token.text_range().start(), - orig_attr, - )); - } - // at this point we won't have any more successful expansions, so stop - break 'expansion; - } - - // Expand fn-like macro calls - if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( - orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast), - spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast), - ) { - let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text()); - let mac_call_path1 = - macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()); - - // inconsistent state, stop expanding - if mac_call_path0 != mac_call_path1 { - break 'expansion; - } - let speculative_args = match macro_call_with_fake_ident.token_tree() { - Some(tt) => tt, - None => break 'expansion, - }; - - match ( - self.sema.expand(&actual_macro_call), - self.sema.speculative_expand( - &actual_macro_call, - &speculative_args, - fake_ident_token.clone(), - ), - ) { - // successful expansions - (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { - let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { - // offset outside of bounds from the original expansion, - // stop here to prevent problems from happening - break 'expansion; - } - original_file = actual_expansion; - speculative_file = fake_expansion; - fake_ident_token = fake_mapped_token; - offset = new_offset; - continue 'expansion; + // maybe parent items have attributes, so continue walking the ancestors + (None, None) => continue 'ancestors, + // successful expansions + (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { + let new_offset = fake_mapped_token.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + // offset outside of bounds from the original expansion, + // stop here to prevent problems from happening + break 'expansion; } - // at least on expansion failed, we won't have anything to expand from this point - // onwards so break out - _ => break 'expansion, + original_file = actual_expansion; + speculative_file = fake_expansion; + fake_ident_token = fake_mapped_token; + offset = new_offset; + continue 'expansion; } + // exactly one expansion failed, inconsistent state so stop expanding completely + _ => break 'expansion, } + } - // none of our states have changed so stop the loop + // No attributes have been expanded, so look for macro_call! token trees or derive token trees + let orig_tt = match find_node_at_offset::(&original_file, offset) { + Some(it) => it, + None => break 'expansion, + }; + let spec_tt = match find_node_at_offset::(&speculative_file, offset) { + Some(it) => it, + None => break 'expansion, + }; + + // Expand pseudo-derive expansion + if let (Some(orig_attr), Some(spec_attr)) = ( + orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), + spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), + ) { + if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = ( + sema.expand_derive_as_pseudo_attr_macro(&orig_attr), + sema.speculative_expand_derive_as_pseudo_attr_macro( + &orig_attr, + &spec_attr, + fake_ident_token.clone(), + ), + ) { + derive_ctx = Some(( + actual_expansion, + fake_expansion, + fake_mapped_token.text_range().start(), + orig_attr, + )); + } + // at this point we won't have any more successful expansions, so stop break 'expansion; } - self.analyze(&original_file, speculative_file, offset, derive_ctx) + // Expand fn-like macro calls + if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( + orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast), + spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast), + ) { + let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text()); + let mac_call_path1 = + macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()); + + // inconsistent state, stop expanding + if mac_call_path0 != mac_call_path1 { + break 'expansion; + } + let speculative_args = match macro_call_with_fake_ident.token_tree() { + Some(tt) => tt, + None => break 'expansion, + }; + + match ( + sema.expand(&actual_macro_call), + sema.speculative_expand( + &actual_macro_call, + &speculative_args, + fake_ident_token.clone(), + ), + ) { + // successful expansions + (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { + let new_offset = fake_mapped_token.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + // offset outside of bounds from the original expansion, + // stop here to prevent problems from happening + break 'expansion; + } + original_file = actual_expansion; + speculative_file = fake_expansion; + fake_ident_token = fake_mapped_token; + offset = new_offset; + continue 'expansion; + } + // at least on expansion failed, we won't have anything to expand from this point + // onwards so break out + _ => break 'expansion, + } + } + + // none of our states have changed so stop the loop + break 'expansion; + } + ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } +} + +/// Fill the completion context, this is what does semantic reasoning about the surrounding context +/// of the completion location. +fn analyze( + sema: &Semantics<'_, RootDatabase>, + expansion_result: ExpansionResult, + original_token: &SyntaxToken, + self_token: &SyntaxToken, +) -> Option<(CompletionAnalysis, (Option, Option), QualifierCtx)> { + let _p = profile::span("CompletionContext::analyze"); + let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } = + expansion_result; + let syntax_element = NodeOrToken::Token(fake_ident_token); + if is_in_token_of_for_loop(syntax_element.clone()) { + // for pat $0 + // there is nothing to complete here except `in` keyword + // don't bother populating the context + // FIXME: the completion calculations should end up good enough + // such that this special case becomes unnecessary + return None; } - /// Calculate the expected type and name of the cursor position. - fn expected_type_and_name( - &self, - name_like: &ast::NameLike, - ) -> (Option, Option) { - let mut node = match self.token.parent() { - Some(it) => it, - None => return (None, None), - }; - - let strip_refs = |mut ty: Type| match name_like { - ast::NameLike::NameRef(n) => { - let p = match n.syntax().parent() { - Some(it) => it, - None => return ty, + // Overwrite the path kind for derives + if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { + if let Some(ast::NameLike::NameRef(name_ref)) = + find_node_at_offset(&file_with_fake_ident, offset) + { + let parent = name_ref.syntax().parent()?; + let (mut nameref_ctx, _) = classify_name_ref(&sema, &original_file, name_ref, parent)?; + if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind { + path_ctx.kind = PathKind::Derive { + existing_derives: sema + .resolve_derive_macro(&origin_attr) + .into_iter() + .flatten() + .flatten() + .collect(), }; - let top_syn = match_ast! { - match p { - ast::FieldExpr(e) => e - .syntax() - .ancestors() - .map_while(ast::FieldExpr::cast) - .last() - .map(|it| it.syntax().clone()), - ast::PathSegment(e) => e - .syntax() - .ancestors() - .skip(1) - .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) - .find_map(ast::PathExpr::cast) - .map(|it| it.syntax().clone()), - _ => None - } - }; - let top_syn = match top_syn { - Some(it) => it, - None => return ty, - }; - for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) { - cov_mark::hit!(expected_type_fn_param_ref); - ty = ty.strip_reference(); - } - ty } - _ => ty, - }; + return Some(( + CompletionAnalysis::NameRef(nameref_ctx), + (None, None), + QualifierCtx::default(), + )); + } + return None; + } - loop { - break match_ast! { - match node { - ast::LetStmt(it) => { - cov_mark::hit!(expected_type_let_with_leading_char); - cov_mark::hit!(expected_type_let_without_leading_char); - let ty = it.pat() - .and_then(|pat| self.sema.type_of_pat(&pat)) - .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it))) - .map(TypeInfo::original); - let name = match it.pat() { - Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name), - Some(_) | None => None, - }; - - (ty, name) - }, - ast::LetExpr(it) => { - cov_mark::hit!(expected_type_if_let_without_leading_char); - let ty = it.pat() - .and_then(|pat| self.sema.type_of_pat(&pat)) - .or_else(|| it.expr().and_then(|it| self.sema.type_of_expr(&it))) - .map(TypeInfo::original); - (ty, None) - }, - ast::ArgList(_) => { - cov_mark::hit!(expected_type_fn_param); - ActiveParameter::at_token( - &self.sema, - self.token.clone(), - ).map(|ap| { - let name = ap.ident().map(NameOrNameRef::Name); - - let ty = strip_refs(ap.ty); - (Some(ty), name) - }) - .unwrap_or((None, None)) - }, - ast::RecordExprFieldList(it) => { - // wouldn't try {} be nice... - (|| { - if self.token.kind() == T![..] - || self.token.prev_token().map(|t| t.kind()) == Some(T![..]) - { - cov_mark::hit!(expected_type_struct_func_update); - let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?; - let ty = self.sema.type_of_expr(&record_expr.into())?; - Some(( - Some(ty.original), - None - )) - } else { - cov_mark::hit!(expected_type_struct_field_without_leading_char); - let expr_field = self.token.prev_sibling_or_token()? - .into_node() - .and_then(ast::RecordExprField::cast)?; - let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?; - Some(( - Some(ty), - expr_field.field_name().map(NameOrNameRef::NameRef), - )) - } - })().unwrap_or((None, None)) - }, - ast::RecordExprField(it) => { - if let Some(expr) = it.expr() { - cov_mark::hit!(expected_type_struct_field_with_leading_char); - ( - self.sema.type_of_expr(&expr).map(TypeInfo::original), - it.field_name().map(NameOrNameRef::NameRef), - ) - } else { - cov_mark::hit!(expected_type_struct_field_followed_by_comma); - let ty = self.sema.resolve_record_field(&it) - .map(|(_, _, ty)| ty); - ( - ty, - it.field_name().map(NameOrNameRef::NameRef), - ) - } - }, - // match foo { $0 } - // match foo { ..., pat => $0 } - ast::MatchExpr(it) => { - let on_arrow = previous_non_trivia_token(self.token.clone()).map_or(false, |it| T![=>] == it.kind()); - - let ty = if on_arrow { - // match foo { ..., pat => $0 } - cov_mark::hit!(expected_type_match_arm_body_without_leading_char); - cov_mark::hit!(expected_type_match_arm_body_with_leading_char); - self.sema.type_of_expr(&it.into()) - } else { - // match foo { $0 } - cov_mark::hit!(expected_type_match_arm_without_leading_char); - it.expr().and_then(|e| self.sema.type_of_expr(&e)) - }.map(TypeInfo::original); - (ty, None) - }, - ast::IfExpr(it) => { - let ty = it.condition() - .and_then(|e| self.sema.type_of_expr(&e)) - .map(TypeInfo::original); - (ty, None) - }, - ast::IdentPat(it) => { - cov_mark::hit!(expected_type_if_let_with_leading_char); - cov_mark::hit!(expected_type_match_arm_with_leading_char); - let ty = self.sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original); - (ty, None) - }, - ast::Fn(it) => { - cov_mark::hit!(expected_type_fn_ret_with_leading_char); - cov_mark::hit!(expected_type_fn_ret_without_leading_char); - let def = self.sema.to_def(&it); - (def.map(|def| def.ret_type(self.db)), None) - }, - ast::ClosureExpr(it) => { - let ty = self.sema.type_of_expr(&it.into()); - ty.and_then(|ty| ty.original.as_callable(self.db)) - .map(|c| (Some(c.return_type()), None)) - .unwrap_or((None, None)) - }, - ast::ParamList(_) => (None, None), - ast::Stmt(_) => (None, None), - ast::Item(_) => (None, None), - _ => { - match node.parent() { - Some(n) => { - node = n; - continue; - }, - None => (None, None), - } - }, + let name_like = match find_node_at_offset(&speculative_file, offset) { + Some(it) => it, + None => { + let analysis = if let Some(original) = ast::String::cast(original_token.clone()) { + CompletionAnalysis::String { + original, + expanded: ast::String::cast(self_token.clone()), + } + } else { + // Fix up trailing whitespace problem + // #[attr(foo = $0 + let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; + let p = token.parent()?; + if p.kind() == SyntaxKind::TOKEN_TREE + && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + { + let colon_prefix = previous_non_trivia_token(self_token.clone()) + .map_or(false, |it| T![:] == it.kind()); + CompletionAnalysis::UnexpandedAttrTT { + fake_attribute_under_caret: syntax_element + .ancestors() + .find_map(ast::Attr::cast), + colon_prefix, + } + } else { + return None; } }; + return Some((analysis, (None, None), QualifierCtx::default())); } - } - - /// Fill the completion context, this is what does semantic reasoning about the surrounding context - /// of the completion location. - fn analyze( - &mut self, - original_file: &SyntaxNode, - file_with_fake_ident: SyntaxNode, - offset: TextSize, - derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>, - ) -> Option { - let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased()?; - let syntax_element = NodeOrToken::Token(fake_ident_token); - if is_in_token_of_for_loop(syntax_element.clone()) { - // for pat $0 - // there is nothing to complete here except `in` keyword - // don't bother populating the context - // FIXME: the completion calculations should end up good enough - // such that this special case becomes unnecessary - return None; + }; + let expected = expected_type_and_name(sema, &self_token, &name_like); + let mut qual_ctx = QualifierCtx::default(); + let analysis = match name_like { + ast::NameLike::Lifetime(lifetime) => { + CompletionAnalysis::Lifetime(classify_lifetime(sema, &original_file, lifetime)?) } + ast::NameLike::NameRef(name_ref) => { + let parent = name_ref.syntax().parent()?; + let (nameref_ctx, qualifier_ctx) = + classify_name_ref(sema, &original_file, name_ref, parent.clone())?; + qual_ctx = qualifier_ctx; + CompletionAnalysis::NameRef(nameref_ctx) + } + ast::NameLike::Name(name) => { + let name_ctx = classify_name(sema, &original_file, name)?; + CompletionAnalysis::Name(name_ctx) + } + }; + Some((analysis, expected, qual_ctx)) +} - // Overwrite the path kind for derives - if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { - if let Some(ast::NameLike::NameRef(name_ref)) = - find_node_at_offset(&file_with_fake_ident, offset) - { - let parent = name_ref.syntax().parent()?; - let (mut nameref_ctx, _) = - Self::classify_name_ref(&self.sema, &original_file, name_ref, parent)?; - if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind { - path_ctx.kind = PathKind::Derive { - existing_derives: self - .sema - .resolve_derive_macro(&origin_attr) - .into_iter() - .flatten() - .flatten() - .collect(), - }; +/// Calculate the expected type and name of the cursor position. +fn expected_type_and_name( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, + name_like: &ast::NameLike, +) -> (Option, Option) { + let mut node = match token.parent() { + Some(it) => it, + None => return (None, None), + }; + + let strip_refs = |mut ty: Type| match name_like { + ast::NameLike::NameRef(n) => { + let p = match n.syntax().parent() { + Some(it) => it, + None => return ty, + }; + let top_syn = match_ast! { + match p { + ast::FieldExpr(e) => e + .syntax() + .ancestors() + .map_while(ast::FieldExpr::cast) + .last() + .map(|it| it.syntax().clone()), + ast::PathSegment(e) => e + .syntax() + .ancestors() + .skip(1) + .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) + .find_map(ast::PathExpr::cast) + .map(|it| it.syntax().clone()), + _ => None } - return Some(CompletionAnalysis::NameRef(nameref_ctx)); + }; + let top_syn = match top_syn { + Some(it) => it, + None => return ty, + }; + for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) { + cov_mark::hit!(expected_type_fn_param_ref); + ty = ty.strip_reference(); } - return None; + ty } + _ => ty, + }; - let name_like = match find_node_at_offset(&file_with_fake_ident, offset) { - Some(it) => it, - None => { - let analysis = - if let Some(original) = ast::String::cast(self.original_token.clone()) { - CompletionAnalysis::String { - original, - expanded: ast::String::cast(self.token.clone()), - } - } else { - // Fix up trailing whitespace problem - // #[attr(foo = $0 - let token = - syntax::algo::skip_trivia_token(self.token.clone(), Direction::Prev)?; - let p = token.parent()?; - if p.kind() == SyntaxKind::TOKEN_TREE - && p.ancestors().any(|it| it.kind() == SyntaxKind::META) - { - let colon_prefix = previous_non_trivia_token(self.token.clone()) - .map_or(false, |it| T![:] == it.kind()); - CompletionAnalysis::UnexpandedAttrTT { - fake_attribute_under_caret: syntax_element - .ancestors() - .find_map(ast::Attr::cast), - colon_prefix, - } - } else { - return None; - } + loop { + break match_ast! { + match node { + ast::LetStmt(it) => { + cov_mark::hit!(expected_type_let_with_leading_char); + cov_mark::hit!(expected_type_let_without_leading_char); + let ty = it.pat() + .and_then(|pat| sema.type_of_pat(&pat)) + .or_else(|| it.initializer().and_then(|it| sema.type_of_expr(&it))) + .map(TypeInfo::original); + let name = match it.pat() { + Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name), + Some(_) | None => None, }; - return Some(analysis); - } - }; - (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like); - let analysis = match name_like { - ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime( - Self::classify_lifetime(&self.sema, original_file, lifetime)?, - ), - ast::NameLike::NameRef(name_ref) => { - let parent = name_ref.syntax().parent()?; - let (nameref_ctx, qualifier_ctx) = - Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone())?; - self.qualifier_ctx = qualifier_ctx; - CompletionAnalysis::NameRef(nameref_ctx) - } - ast::NameLike::Name(name) => { - let name_ctx = Self::classify_name(&self.sema, original_file, name)?; - CompletionAnalysis::Name(name_ctx) - } - }; - Some(analysis) - } - - fn classify_lifetime( - _sema: &Semantics<'_, RootDatabase>, - original_file: &SyntaxNode, - lifetime: ast::Lifetime, - ) -> Option { - let parent = lifetime.syntax().parent()?; - if parent.kind() == SyntaxKind::ERROR { - return None; - } - - let kind = match_ast! { - match parent { - ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { - is_decl: param.lifetime().as_ref() == Some(&lifetime), - param + (ty, name) }, - ast::BreakExpr(_) => LifetimeKind::LabelRef, - ast::ContinueExpr(_) => LifetimeKind::LabelRef, - ast::Label(_) => LifetimeKind::LabelDef, - _ => LifetimeKind::Lifetime, - } - }; - let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start()); + ast::LetExpr(it) => { + cov_mark::hit!(expected_type_if_let_without_leading_char); + let ty = it.pat() + .and_then(|pat| sema.type_of_pat(&pat)) + .or_else(|| it.expr().and_then(|it| sema.type_of_expr(&it))) + .map(TypeInfo::original); + (ty, None) + }, + ast::ArgList(_) => { + cov_mark::hit!(expected_type_fn_param); + ActiveParameter::at_token( + &sema, + token.clone(), + ).map(|ap| { + let name = ap.ident().map(NameOrNameRef::Name); - Some(LifetimeContext { lifetime, kind }) - } - - fn classify_name( - sema: &Semantics<'_, RootDatabase>, - original_file: &SyntaxNode, - name: ast::Name, - ) -> Option { - let parent = name.syntax().parent()?; - let kind = match_ast! { - match parent { - ast::Const(_) => NameKind::Const, - ast::ConstParam(_) => NameKind::ConstParam, - ast::Enum(_) => NameKind::Enum, - ast::Fn(_) => NameKind::Function, - ast::IdentPat(bind_pat) => { - let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); - if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { - pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat()); + let ty = strip_refs(ap.ty); + (Some(ty), name) + }) + .unwrap_or((None, None)) + }, + ast::RecordExprFieldList(it) => { + // wouldn't try {} be nice... + (|| { + if token.kind() == T![..] + ||token.prev_token().map(|t| t.kind()) == Some(T![..]) + { + cov_mark::hit!(expected_type_struct_func_update); + let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?; + let ty = sema.type_of_expr(&record_expr.into())?; + Some(( + Some(ty.original), + None + )) + } else { + cov_mark::hit!(expected_type_struct_field_without_leading_char); + let expr_field = token.prev_sibling_or_token()? + .into_node() + .and_then(ast::RecordExprField::cast)?; + let (_, _, ty) = sema.resolve_record_field(&expr_field)?; + Some(( + Some(ty), + expr_field.field_name().map(NameOrNameRef::NameRef), + )) + } + })().unwrap_or((None, None)) + }, + ast::RecordExprField(it) => { + if let Some(expr) = it.expr() { + cov_mark::hit!(expected_type_struct_field_with_leading_char); + ( + sema.type_of_expr(&expr).map(TypeInfo::original), + it.field_name().map(NameOrNameRef::NameRef), + ) + } else { + cov_mark::hit!(expected_type_struct_field_followed_by_comma); + let ty = sema.resolve_record_field(&it) + .map(|(_, _, ty)| ty); + ( + ty, + it.field_name().map(NameOrNameRef::NameRef), + ) + } + }, + // match foo { $0 } + // match foo { ..., pat => $0 } + ast::MatchExpr(it) => { + let on_arrow = previous_non_trivia_token(token.clone()).map_or(false, |it| T![=>] == it.kind()); + + let ty = if on_arrow { + // match foo { ..., pat => $0 } + cov_mark::hit!(expected_type_match_arm_body_without_leading_char); + cov_mark::hit!(expected_type_match_arm_body_with_leading_char); + sema.type_of_expr(&it.into()) + } else { + // match foo { $0 } + cov_mark::hit!(expected_type_match_arm_without_leading_char); + it.expr().and_then(|e| sema.type_of_expr(&e)) + }.map(TypeInfo::original); + (ty, None) + }, + ast::IfExpr(it) => { + let ty = it.condition() + .and_then(|e| sema.type_of_expr(&e)) + .map(TypeInfo::original); + (ty, None) + }, + ast::IdentPat(it) => { + cov_mark::hit!(expected_type_if_let_with_leading_char); + cov_mark::hit!(expected_type_match_arm_with_leading_char); + let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original); + (ty, None) + }, + ast::Fn(it) => { + cov_mark::hit!(expected_type_fn_ret_with_leading_char); + cov_mark::hit!(expected_type_fn_ret_without_leading_char); + let def = sema.to_def(&it); + (def.map(|def| def.ret_type(sema.db)), None) + }, + ast::ClosureExpr(it) => { + let ty = sema.type_of_expr(&it.into()); + ty.and_then(|ty| ty.original.as_callable(sema.db)) + .map(|c| (Some(c.return_type()), None)) + .unwrap_or((None, None)) + }, + ast::ParamList(_) => (None, None), + ast::Stmt(_) => (None, None), + ast::Item(_) => (None, None), + _ => { + match node.parent() { + Some(n) => { + node = n; + continue; + }, + None => (None, None), } - - NameKind::IdentPat(pat_ctx) }, - ast::MacroDef(_) => NameKind::MacroDef, - ast::MacroRules(_) => NameKind::MacroRules, - ast::Module(module) => NameKind::Module(module), - ast::RecordField(_) => NameKind::RecordField, - ast::Rename(_) => NameKind::Rename, - ast::SelfParam(_) => NameKind::SelfParam, - ast::Static(_) => NameKind::Static, - ast::Struct(_) => NameKind::Struct, - ast::Trait(_) => NameKind::Trait, - ast::TypeAlias(_) => NameKind::TypeAlias, - ast::TypeParam(_) => NameKind::TypeParam, - ast::Union(_) => NameKind::Union, - ast::Variant(_) => NameKind::Variant, - _ => return None, } }; - let name = find_node_at_offset(&original_file, name.syntax().text_range().start()); - Some(NameContext { name, kind }) + } +} + +fn classify_lifetime( + _sema: &Semantics<'_, RootDatabase>, + original_file: &SyntaxNode, + lifetime: ast::Lifetime, +) -> Option { + let parent = lifetime.syntax().parent()?; + if parent.kind() == SyntaxKind::ERROR { + return None; } - fn classify_name_ref( - sema: &Semantics<'_, RootDatabase>, - original_file: &SyntaxNode, - name_ref: ast::NameRef, - parent: SyntaxNode, - ) -> Option<(NameRefContext, QualifierCtx)> { - let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); + let kind = match_ast! { + match parent { + ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { + is_decl: param.lifetime().as_ref() == Some(&lifetime), + param + }, + ast::BreakExpr(_) => LifetimeKind::LabelRef, + ast::ContinueExpr(_) => LifetimeKind::LabelRef, + ast::Label(_) => LifetimeKind::LabelDef, + _ => LifetimeKind::Lifetime, + } + }; + let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start()); - let make_res = - |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default()); + Some(LifetimeContext { lifetime, kind }) +} - if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { - let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone()) - .map_or(false, |it| T![.] == it.kind()); +fn classify_name( + sema: &Semantics<'_, RootDatabase>, + original_file: &SyntaxNode, + name: ast::Name, +) -> Option { + let parent = name.syntax().parent()?; + let kind = match_ast! { + match parent { + ast::Const(_) => NameKind::Const, + ast::ConstParam(_) => NameKind::ConstParam, + ast::Enum(_) => NameKind::Enum, + ast::Fn(_) => NameKind::Function, + ast::IdentPat(bind_pat) => { + let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); + if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { + pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat()); + } - return find_node_in_file_compensated( + NameKind::IdentPat(pat_ctx) + }, + ast::MacroDef(_) => NameKind::MacroDef, + ast::MacroRules(_) => NameKind::MacroRules, + ast::Module(module) => NameKind::Module(module), + ast::RecordField(_) => NameKind::RecordField, + ast::Rename(_) => NameKind::Rename, + ast::SelfParam(_) => NameKind::SelfParam, + ast::Static(_) => NameKind::Static, + ast::Struct(_) => NameKind::Struct, + ast::Trait(_) => NameKind::Trait, + ast::TypeAlias(_) => NameKind::TypeAlias, + ast::TypeParam(_) => NameKind::TypeParam, + ast::Union(_) => NameKind::Union, + ast::Variant(_) => NameKind::Variant, + _ => return None, + } + }; + let name = find_node_at_offset(&original_file, name.syntax().text_range().start()); + Some(NameContext { name, kind }) +} + +fn classify_name_ref( + sema: &Semantics<'_, RootDatabase>, + original_file: &SyntaxNode, + name_ref: ast::NameRef, + parent: SyntaxNode, +) -> Option<(NameRefContext, QualifierCtx)> { + let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); + + let make_res = |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default()); + + if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { + let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone()) + .map_or(false, |it| T![.] == it.kind()); + + return find_node_in_file_compensated( + sema, + original_file, + &record_field.parent_record_lit(), + ) + .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix }) + .map(make_res); + } + if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { + let kind = NameRefKind::Pattern(PatternContext { + param_ctx: None, + has_type_ascription: false, + ref_token: None, + mut_token: None, + record_pat: find_node_in_file_compensated( sema, original_file, - &record_field.parent_record_lit(), + &record_field.parent_record_pat(), + ), + ..pattern_context_for( + sema, + original_file, + record_field.parent_record_pat().clone().into(), ) - .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix }) - .map(make_res); - } - if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { - let kind = NameRefKind::Pattern(PatternContext { - param_ctx: None, - has_type_ascription: false, - ref_token: None, - mut_token: None, - record_pat: find_node_in_file_compensated( - sema, - original_file, - &record_field.parent_record_pat(), - ), - ..pattern_context_for( - sema, - original_file, - record_field.parent_record_pat().clone().into(), - ) - }); - return Some(make_res(kind)); - } + }); + return Some(make_res(kind)); + } - let segment = match_ast! { + let segment = match_ast! { + match parent { + ast::PathSegment(segment) => segment, + ast::FieldExpr(field) => { + let receiver = find_opt_node_in_file(original_file, field.expr()); + let receiver_is_ambiguous_float_literal = match &receiver { + Some(ast::Expr::Literal(l)) => matches! { + l.kind(), + ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.')) + }, + _ => false, + }; + let kind = NameRefKind::DotAccess(DotAccess { + receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), + kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, + receiver + }); + return Some(make_res(kind)); + }, + ast::MethodCallExpr(method) => { + let receiver = find_opt_node_in_file(original_file, method.receiver()); + let kind = NameRefKind::DotAccess(DotAccess { + receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), + kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, + receiver + }); + return Some(make_res(kind)); + }, + _ => return None, + } + }; + + let path = segment.parent_path(); + let original_path = find_node_in_file_compensated(sema, original_file, &path); + + let mut path_ctx = PathCompletionCtx { + has_call_parens: false, + has_macro_bang: false, + qualified: Qualified::No, + parent: None, + path: path.clone(), + original_path, + kind: PathKind::Item { kind: ItemListKind::SourceFile }, + has_type_args: false, + use_tree_parent: false, + }; + + let is_in_block = |it: &SyntaxNode| { + it.parent() + .map(|node| { + ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind()) + }) + .unwrap_or(false) + }; + let func_update_record = |syn: &SyntaxNode| { + if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { + find_node_in_file_compensated(sema, original_file, &record_expr) + } else { + None + } + }; + let after_if_expr = |node: SyntaxNode| { + let prev_expr = (|| { + let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; + ast::ExprStmt::cast(prev_sibling)?.expr() + })(); + matches!(prev_expr, Some(ast::Expr::IfExpr(_))) + }; + + // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. + // ex. trait Foo $0 {} + // in these cases parser recovery usually kicks in for our inserted identifier, causing it + // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block + // expression or an item list. + // The following code checks if the body is missing, if it is we either cut off the body + // from the item or it was missing in the first place + let inbetween_body_and_decl_check = |node: SyntaxNode| { + if let Some(NodeOrToken::Node(n)) = + syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev) + { + if let Some(item) = ast::Item::cast(n) { + let is_inbetween = match &item { + ast::Item::Const(it) => it.body().is_none(), + ast::Item::Enum(it) => it.variant_list().is_none(), + ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), + ast::Item::Fn(it) => it.body().is_none(), + ast::Item::Impl(it) => it.assoc_item_list().is_none(), + ast::Item::Module(it) => it.item_list().is_none(), + ast::Item::Static(it) => it.body().is_none(), + ast::Item::Struct(it) => it.field_list().is_none(), + ast::Item::Trait(it) => it.assoc_item_list().is_none(), + ast::Item::TypeAlias(it) => it.ty().is_none(), + ast::Item::Union(it) => it.record_field_list().is_none(), + _ => false, + }; + if is_inbetween { + return Some(item); + } + } + } + None + }; + + let type_location = |node: &SyntaxNode| { + let parent = node.parent()?; + let res = match_ast! { match parent { - ast::PathSegment(segment) => segment, - ast::FieldExpr(field) => { - let receiver = find_opt_node_in_file(original_file, field.expr()); - let receiver_is_ambiguous_float_literal = match &receiver { - Some(ast::Expr::Literal(l)) => matches! { - l.kind(), - ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.')) - }, - _ => false, + ast::Const(it) => { + let name = find_opt_node_in_file(original_file, it.name())?; + let original = ast::Const::cast(name.syntax().parent()?)?; + TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) + }, + ast::RetType(it) => { + if it.thin_arrow_token().is_none() { + return None; + } + let parent = match ast::Fn::cast(parent.parent()?) { + Some(x) => x.param_list(), + None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), }; - let kind = NameRefKind::DotAccess(DotAccess { - receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), - kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, - receiver - }); - return Some(make_res(kind)); + + let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { + match parent { + ast::ClosureExpr(it) => { + it.body() + }, + ast::Fn(it) => { + it.body().map(ast::Expr::BlockExpr) + }, + _ => return None, + } + })) }, - ast::MethodCallExpr(method) => { - let receiver = find_opt_node_in_file(original_file, method.receiver()); - let kind = NameRefKind::DotAccess(DotAccess { - receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), - kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, - receiver - }); - return Some(make_res(kind)); + ast::Param(it) => { + if it.colon_token().is_none() { + return None; + } + TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat()))) }, + ast::LetStmt(it) => { + if it.colon_token().is_none() { + return None; + } + TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat()))) + }, + ast::Impl(it) => { + match it.trait_() { + Some(t) if t.syntax() == node => TypeLocation::ImplTrait, + _ => match it.self_ty() { + Some(t) if t.syntax() == node => TypeLocation::ImplTarget, + _ => return None, + }, + } + }, + ast::TypeBound(_) => TypeLocation::TypeBound, + // is this case needed? + ast::TypeBoundList(_) => TypeLocation::TypeBound, + ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), + // is this case needed? + ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))), + ast::TupleField(_) => TypeLocation::TupleField, _ => return None, } }; + Some(res) + }; - let path = segment.parent_path(); - let original_path = find_node_in_file_compensated(sema, original_file, &path); - - let mut path_ctx = PathCompletionCtx { - has_call_parens: false, - has_macro_bang: false, - qualified: Qualified::No, - parent: None, - path: path.clone(), - original_path, - kind: PathKind::Item { kind: ItemListKind::SourceFile }, - has_type_args: false, - use_tree_parent: false, - }; - - let is_in_block = |it: &SyntaxNode| { - it.parent() - .map(|node| { - ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind()) - }) - .unwrap_or(false) - }; - let func_update_record = |syn: &SyntaxNode| { - if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { - find_node_in_file_compensated(sema, original_file, &record_expr) + let is_in_condition = |it: &ast::Expr| { + (|| { + let parent = it.syntax().parent()?; + if let Some(expr) = ast::WhileExpr::cast(parent.clone()) { + Some(expr.condition()? == *it) + } else if let Some(expr) = ast::IfExpr::cast(parent) { + Some(expr.condition()? == *it) } else { None } - }; - let after_if_expr = |node: SyntaxNode| { - let prev_expr = (|| { - let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; - ast::ExprStmt::cast(prev_sibling)?.expr() - })(); - matches!(prev_expr, Some(ast::Expr::IfExpr(_))) - }; + })() + .unwrap_or(false) + }; - // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. - // ex. trait Foo $0 {} - // in these cases parser recovery usually kicks in for our inserted identifier, causing it - // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block - // expression or an item list. - // The following code checks if the body is missing, if it is we either cut off the body - // from the item or it was missing in the first place - let inbetween_body_and_decl_check = |node: SyntaxNode| { - if let Some(NodeOrToken::Node(n)) = - syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev) - { - if let Some(item) = ast::Item::cast(n) { - let is_inbetween = match &item { - ast::Item::Const(it) => it.body().is_none(), - ast::Item::Enum(it) => it.variant_list().is_none(), - ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), - ast::Item::Fn(it) => it.body().is_none(), - ast::Item::Impl(it) => it.assoc_item_list().is_none(), - ast::Item::Module(it) => it.item_list().is_none(), - ast::Item::Static(it) => it.body().is_none(), - ast::Item::Struct(it) => it.field_list().is_none(), - ast::Item::Trait(it) => it.assoc_item_list().is_none(), - ast::Item::TypeAlias(it) => it.ty().is_none(), - ast::Item::Union(it) => it.record_field_list().is_none(), - _ => false, - }; - if is_inbetween { - return Some(item); + let make_path_kind_expr = |expr: ast::Expr| { + let it = expr.syntax(); + let in_block_expr = is_in_block(it); + let in_loop_body = is_in_loop_body(it); + let after_if_expr = after_if_expr(it.clone()); + let ref_expr_parent = + path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast); + let (innermost_ret_ty, self_param) = { + let find_ret_ty = |it: SyntaxNode| { + if let Some(item) = ast::Item::cast(it.clone()) { + match item { + ast::Item::Fn(f) => Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))), + ast::Item::MacroCall(_) => None, + _ => Some(None), } - } - } - None - }; - - let type_location = |node: &SyntaxNode| { - let parent = node.parent()?; - let res = match_ast! { - match parent { - ast::Const(it) => { - let name = find_opt_node_in_file(original_file, it.name())?; - let original = ast::Const::cast(name.syntax().parent()?)?; - TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) - }, - ast::RetType(it) => { - if it.thin_arrow_token().is_none() { - return None; - } - let parent = match ast::Fn::cast(parent.parent()?) { - Some(x) => x.param_list(), - None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), - }; - - let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; - TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { - match parent { - ast::ClosureExpr(it) => { - it.body() - }, - ast::Fn(it) => { - it.body().map(ast::Expr::BlockExpr) - }, - _ => return None, - } - })) - }, - ast::Param(it) => { - if it.colon_token().is_none() { - return None; - } - TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat()))) - }, - ast::LetStmt(it) => { - if it.colon_token().is_none() { - return None; - } - TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat()))) - }, - ast::Impl(it) => { - match it.trait_() { - Some(t) if t.syntax() == node => TypeLocation::ImplTrait, - _ => match it.self_ty() { - Some(t) if t.syntax() == node => TypeLocation::ImplTarget, - _ => return None, - }, - } - }, - ast::TypeBound(_) => TypeLocation::TypeBound, - // is this case needed? - ast::TypeBoundList(_) => TypeLocation::TypeBound, - ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), - // is this case needed? - ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))), - ast::TupleField(_) => TypeLocation::TupleField, - _ => return None, - } - }; - Some(res) - }; - - let is_in_condition = |it: &ast::Expr| { - (|| { - let parent = it.syntax().parent()?; - if let Some(expr) = ast::WhileExpr::cast(parent.clone()) { - Some(expr.condition()? == *it) - } else if let Some(expr) = ast::IfExpr::cast(parent) { - Some(expr.condition()? == *it) } else { - None - } - })() - .unwrap_or(false) - }; - - let make_path_kind_expr = |expr: ast::Expr| { - let it = expr.syntax(); - let in_block_expr = is_in_block(it); - let in_loop_body = is_in_loop_body(it); - let after_if_expr = after_if_expr(it.clone()); - let ref_expr_parent = - path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast); - let (innermost_ret_ty, self_param) = { - let find_ret_ty = |it: SyntaxNode| { - if let Some(item) = ast::Item::cast(it.clone()) { - match item { - ast::Item::Fn(f) => { - Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))) - } - ast::Item::MacroCall(_) => None, - _ => Some(None), - } - } else { - let expr = ast::Expr::cast(it)?; - let callable = match expr { - // FIXME - // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b), - ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr), - _ => return None, - }; - Some( - callable - .and_then(|c| c.adjusted().as_callable(sema.db)) - .map(|it| it.return_type()), - ) - } - }; - let find_fn_self_param = |it| match it { - ast::Item::Fn(fn_) => { - Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))) - } - ast::Item::MacroCall(_) => None, - _ => Some(None), - }; - - match find_node_in_file_compensated(sema, original_file, &expr) { - Some(it) => { - let innermost_ret_ty = sema - .ancestors_with_macros(it.syntax().clone()) - .find_map(find_ret_ty) - .flatten(); - - let self_param = sema - .ancestors_with_macros(it.syntax().clone()) - .filter_map(ast::Item::cast) - .find_map(find_fn_self_param) - .flatten(); - (innermost_ret_ty, self_param) - } - None => (None, None), + let expr = ast::Expr::cast(it)?; + let callable = match expr { + // FIXME + // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b), + ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr), + _ => return None, + }; + Some( + callable + .and_then(|c| c.adjusted().as_callable(sema.db)) + .map(|it| it.return_type()), + ) } }; - let is_func_update = func_update_record(it); - let in_condition = is_in_condition(&expr); - let incomplete_let = it - .parent() - .and_then(ast::LetStmt::cast) - .map_or(false, |it| it.semicolon_token().is_none()); - let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax()); - - let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) { - Some(arm) => arm - .fat_arrow_token() - .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()), - None => false, + let find_fn_self_param = |it| match it { + ast::Item::Fn(fn_) => Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))), + ast::Item::MacroCall(_) => None, + _ => Some(None), }; - PathKind::Expr { - expr_ctx: ExprCtx { - in_block_expr, - in_loop_body, - after_if_expr, - in_condition, - ref_expr_parent, - is_func_update, - innermost_ret_ty, - self_param, - incomplete_let, - impl_, - in_match_guard, - }, + match find_node_in_file_compensated(sema, original_file, &expr) { + Some(it) => { + let innermost_ret_ty = sema + .ancestors_with_macros(it.syntax().clone()) + .find_map(find_ret_ty) + .flatten(); + + let self_param = sema + .ancestors_with_macros(it.syntax().clone()) + .filter_map(ast::Item::cast) + .find_map(find_fn_self_param) + .flatten(); + (innermost_ret_ty, self_param) + } + None => (None, None), } }; - let make_path_kind_type = |ty: ast::Type| { - let location = type_location(ty.syntax()); - PathKind::Type { location: location.unwrap_or(TypeLocation::Other) } + let is_func_update = func_update_record(it); + let in_condition = is_in_condition(&expr); + let incomplete_let = it + .parent() + .and_then(ast::LetStmt::cast) + .map_or(false, |it| it.semicolon_token().is_none()); + let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax()); + + let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) { + Some(arm) => arm + .fat_arrow_token() + .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()), + None => false, }; - let mut kind_macro_call = |it: ast::MacroCall| { - path_ctx.has_macro_bang = it.excl_token().is_some(); - let parent = it.syntax().parent()?; - // Any path in an item list will be treated as a macro call by the parser - let kind = match_ast! { - match parent { - ast::MacroExpr(expr) => make_path_kind_expr(expr.into()), - ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}, - ast::MacroType(ty) => make_path_kind_type(ty.into()), - ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module }, - ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() { - Some(it) => match_ast! { - match it { - ast::Trait(_) => ItemListKind::Trait, - ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it)) - } else { - ItemListKind::Impl - }, - _ => return None - } - }, - None => return None, - } }, - ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock }, - ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile }, - _ => return None, - } - }; - Some(kind) - }; - let make_path_kind_attr = |meta: ast::Meta| { - let attr = meta.parent_attr()?; - let kind = attr.kind(); - let attached = attr.syntax().parent()?; - let is_trailing_outer_attr = kind != AttrKind::Inner - && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next) - .is_none(); - let annotated_item_kind = - if is_trailing_outer_attr { None } else { Some(attached.kind()) }; - Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) - }; + PathKind::Expr { + expr_ctx: ExprCtx { + in_block_expr, + in_loop_body, + after_if_expr, + in_condition, + ref_expr_parent, + is_func_update, + innermost_ret_ty, + self_param, + incomplete_let, + impl_, + in_match_guard, + }, + } + }; + let make_path_kind_type = |ty: ast::Type| { + let location = type_location(ty.syntax()); + PathKind::Type { location: location.unwrap_or(TypeLocation::Other) } + }; - // Infer the path kind - let parent = path.syntax().parent()?; + let mut kind_macro_call = |it: ast::MacroCall| { + path_ctx.has_macro_bang = it.excl_token().is_some(); + let parent = it.syntax().parent()?; + // Any path in an item list will be treated as a macro call by the parser let kind = match_ast! { match parent { - ast::PathType(it) => make_path_kind_type(it.into()), - ast::PathExpr(it) => { - if let Some(p) = it.syntax().parent() { - if ast::ExprStmt::can_cast(p.kind()) { - if let Some(kind) = inbetween_body_and_decl_check(p) { - return Some(make_res(NameRefKind::Keyword(kind))); - } + ast::MacroExpr(expr) => make_path_kind_expr(expr.into()), + ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}, + ast::MacroType(ty) => make_path_kind_type(ty.into()), + ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module }, + ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() { + Some(it) => match_ast! { + match it { + ast::Trait(_) => ItemListKind::Trait, + ast::Impl(it) => if it.trait_().is_some() { + ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it)) + } else { + ItemListKind::Impl + }, + _ => return None } - } - - path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); - - make_path_kind_expr(it.into()) - }, - ast::TupleStructPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::RecordPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::PathPat(it) => { - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} - }, - ast::MacroCall(it) => { - // A macro call in this position is usually a result of parsing recovery, so check that - if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { - return Some(make_res(NameRefKind::Keyword(kind))); - } - - kind_macro_call(it)? - }, - ast::Meta(meta) => make_path_kind_attr(meta)?, - ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, - ast::UseTree(_) => PathKind::Use, - // completing inside a qualifier - ast::Path(parent) => { - path_ctx.parent = Some(parent.clone()); - let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?; - match_ast! { - match parent { - ast::PathType(it) => make_path_kind_type(it.into()), - ast::PathExpr(it) => { - path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); - - make_path_kind_expr(it.into()) - }, - ast::TupleStructPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::RecordPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::PathPat(it) => { - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} - }, - ast::MacroCall(it) => { - kind_macro_call(it)? - }, - ast::Meta(meta) => make_path_kind_attr(meta)?, - ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, - ast::UseTree(_) => PathKind::Use, - ast::RecordExpr(it) => make_path_kind_expr(it.into()), - _ => return None, - } - } - }, - ast::RecordExpr(it) => make_path_kind_expr(it.into()), + }, + None => return None, + } }, + ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock }, + ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile }, _ => return None, } }; + Some(kind) + }; + let make_path_kind_attr = |meta: ast::Meta| { + let attr = meta.parent_attr()?; + let kind = attr.kind(); + let attached = attr.syntax().parent()?; + let is_trailing_outer_attr = kind != AttrKind::Inner + && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none(); + let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) }; + Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) + }; - path_ctx.kind = kind; - path_ctx.has_type_args = segment.generic_arg_list().is_some(); - - // calculate the qualifier context - if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) { - path_ctx.use_tree_parent = use_tree_parent; - if !use_tree_parent && segment.coloncolon_token().is_some() { - path_ctx.qualified = Qualified::Absolute; - } else { - let qualifier = qualifier - .segment() - .and_then(|it| find_node_in_file(original_file, &it)) - .map(|it| it.parent_path()); - if let Some(qualifier) = qualifier { - let type_anchor = match qualifier.segment().and_then(|it| it.kind()) { - Some(ast::PathSegmentKind::Type { - type_ref: Some(type_ref), - trait_ref, - }) if qualifier.qualifier().is_none() => Some((type_ref, trait_ref)), - _ => None, - }; - - path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor { - let ty = match ty { - ast::Type::InferType(_) => None, - ty => sema.resolve_type(&ty), - }; - let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?)); - Qualified::TypeAnchor { ty, trait_ } - } else { - let res = sema.resolve_path(&qualifier); - - // For understanding how and why super_chain_len is calculated the way it - // is check the documentation at it's definition - let mut segment_count = 0; - let super_count = - iter::successors(Some(qualifier.clone()), |p| p.qualifier()) - .take_while(|p| { - p.segment() - .and_then(|s| { - segment_count += 1; - s.super_token() - }) - .is_some() - }) - .count(); - - let super_chain_len = - if segment_count > super_count { None } else { Some(super_count) }; - - Qualified::With { path: qualifier, resolution: res, super_chain_len } - } - }; - } - } else if let Some(segment) = path.segment() { - if segment.coloncolon_token().is_some() { - path_ctx.qualified = Qualified::Absolute; - } - } - - let mut qualifier_ctx = QualifierCtx::default(); - if path_ctx.is_trivial_path() { - // fetch the full expression that may have qualifiers attached to it - let top_node = match path_ctx.kind { - PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => { - parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| { - let parent = p.parent()?; - if ast::StmtList::can_cast(parent.kind()) { - Some(p) - } else if ast::ExprStmt::can_cast(parent.kind()) { - Some(parent) - } else { - None + // Infer the path kind + let parent = path.syntax().parent()?; + let kind = match_ast! { + match parent { + ast::PathType(it) => make_path_kind_type(it.into()), + ast::PathExpr(it) => { + if let Some(p) = it.syntax().parent() { + if ast::ExprStmt::can_cast(p.kind()) { + if let Some(kind) = inbetween_body_and_decl_check(p) { + return Some(make_res(NameRefKind::Keyword(kind))); } - }) - } - PathKind::Item { .. } => { - parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) - } - _ => None, - }; - if let Some(top) = top_node { - if let Some(NodeOrToken::Node(error_node)) = - syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev) - { - if error_node.kind() == SyntaxKind::ERROR { - qualifier_ctx.unsafe_tok = error_node - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|it| it.kind() == T![unsafe]); - qualifier_ctx.vis_node = - error_node.children().find_map(ast::Visibility::cast); } } - if let PathKind::Item { .. } = path_ctx.kind { - if qualifier_ctx.none() { - if let Some(t) = top.first_token() { - if let Some(prev) = t - .prev_token() - .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) - { - if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) { - // This was inferred to be an item position path, but it seems - // to be part of some other broken node which leaked into an item - // list - return None; - } + path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); + + make_path_kind_expr(it.into()) + }, + ast::TupleStructPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::RecordPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::PathPat(it) => { + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} + }, + ast::MacroCall(it) => { + // A macro call in this position is usually a result of parsing recovery, so check that + if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { + return Some(make_res(NameRefKind::Keyword(kind))); + } + + kind_macro_call(it)? + }, + ast::Meta(meta) => make_path_kind_attr(meta)?, + ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, + ast::UseTree(_) => PathKind::Use, + // completing inside a qualifier + ast::Path(parent) => { + path_ctx.parent = Some(parent.clone()); + let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?; + match_ast! { + match parent { + ast::PathType(it) => make_path_kind_type(it.into()), + ast::PathExpr(it) => { + path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); + + make_path_kind_expr(it.into()) + }, + ast::TupleStructPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::RecordPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::PathPat(it) => { + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} + }, + ast::MacroCall(it) => { + kind_macro_call(it)? + }, + ast::Meta(meta) => make_path_kind_attr(meta)?, + ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, + ast::UseTree(_) => PathKind::Use, + ast::RecordExpr(it) => make_path_kind_expr(it.into()), + _ => return None, + } + } + }, + ast::RecordExpr(it) => make_path_kind_expr(it.into()), + _ => return None, + } + }; + + path_ctx.kind = kind; + path_ctx.has_type_args = segment.generic_arg_list().is_some(); + + // calculate the qualifier context + if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) { + path_ctx.use_tree_parent = use_tree_parent; + if !use_tree_parent && segment.coloncolon_token().is_some() { + path_ctx.qualified = Qualified::Absolute; + } else { + let qualifier = qualifier + .segment() + .and_then(|it| find_node_in_file(original_file, &it)) + .map(|it| it.parent_path()); + if let Some(qualifier) = qualifier { + let type_anchor = match qualifier.segment().and_then(|it| it.kind()) { + Some(ast::PathSegmentKind::Type { type_ref: Some(type_ref), trait_ref }) + if qualifier.qualifier().is_none() => + { + Some((type_ref, trait_ref)) + } + _ => None, + }; + + path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor { + let ty = match ty { + ast::Type::InferType(_) => None, + ty => sema.resolve_type(&ty), + }; + let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?)); + Qualified::TypeAnchor { ty, trait_ } + } else { + let res = sema.resolve_path(&qualifier); + + // For understanding how and why super_chain_len is calculated the way it + // is check the documentation at it's definition + let mut segment_count = 0; + let super_count = iter::successors(Some(qualifier.clone()), |p| p.qualifier()) + .take_while(|p| { + p.segment() + .and_then(|s| { + segment_count += 1; + s.super_token() + }) + .is_some() + }) + .count(); + + let super_chain_len = + if segment_count > super_count { None } else { Some(super_count) }; + + Qualified::With { path: qualifier, resolution: res, super_chain_len } + } + }; + } + } else if let Some(segment) = path.segment() { + if segment.coloncolon_token().is_some() { + path_ctx.qualified = Qualified::Absolute; + } + } + + let mut qualifier_ctx = QualifierCtx::default(); + if path_ctx.is_trivial_path() { + // fetch the full expression that may have qualifiers attached to it + let top_node = match path_ctx.kind { + PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => { + parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| { + let parent = p.parent()?; + if ast::StmtList::can_cast(parent.kind()) { + Some(p) + } else if ast::ExprStmt::can_cast(parent.kind()) { + Some(parent) + } else { + None + } + }) + } + PathKind::Item { .. } => { + parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) + } + _ => None, + }; + if let Some(top) = top_node { + if let Some(NodeOrToken::Node(error_node)) = + syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev) + { + if error_node.kind() == SyntaxKind::ERROR { + qualifier_ctx.unsafe_tok = error_node + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|it| it.kind() == T![unsafe]); + qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast); + } + } + + if let PathKind::Item { .. } = path_ctx.kind { + if qualifier_ctx.none() { + if let Some(t) = top.first_token() { + if let Some(prev) = t + .prev_token() + .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) + { + if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) { + // This was inferred to be an item position path, but it seems + // to be part of some other broken node which leaked into an item + // list + return None; } } } } } } - Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx)) } + Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx)) } fn pattern_context_for( diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 8d21f4fce0..9d0044e55f 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -183,6 +183,7 @@ pub fn completions( CompletionAnalysis::String { original, expanded: Some(expanded) } => { completions::extern_abi::complete_extern_abi(acc, ctx, expanded); completions::format_string::format_string(acc, ctx, original, expanded); + completions::env_vars::complete_cargo_env_vars(acc, ctx, expanded); } CompletionAnalysis::UnexpandedAttrTT { colon_prefix, diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml index 30272bc16f..cf0bcd5c96 100644 --- a/crates/ide-db/Cargo.toml +++ b/crates/ide-db/Cargo.toml @@ -15,9 +15,9 @@ tracing = "0.1.35" rayon = "1.5.3" fst = { version = "0.4.7", default-features = false } rustc-hash = "1.1.0" -once_cell = "1.12.0" +once_cell = "1.15.0" either = "1.7.0" -itertools = "0.10.3" +itertools = "0.10.5" arrayvec = "0.7.2" indexmap = "1.9.1" memchr = "2.5.0" diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs index 7fb4b90e6d..371d642c15 100644 --- a/crates/ide-db/src/imports/merge_imports.rs +++ b/crates/ide-db/src/imports/merge_imports.rs @@ -225,7 +225,7 @@ fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering { } /// Compares two paths, if one ends earlier than the other the has_tl parameters decide which is -/// greater as a a path that has a tree list should be greater, while one that just ends without +/// greater as a path that has a tree list should be greater, while one that just ends without /// a tree list should be considered less. pub(super) fn use_tree_path_cmp( a: &ast::Path, diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index 7cabdb55e8..82b85f2fa5 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -239,6 +239,7 @@ impl Definition { DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()), DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()), DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()), + DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()), }; return match def { Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), diff --git a/crates/ide-db/src/syntax_helpers/format_string.rs b/crates/ide-db/src/syntax_helpers/format_string.rs index f48a570086..2d6927cee9 100644 --- a/crates/ide-db/src/syntax_helpers/format_string.rs +++ b/crates/ide-db/src/syntax_helpers/format_string.rs @@ -1,7 +1,8 @@ //! Tools to work with format string literals for the `format_args!` family of macros. +use crate::syntax_helpers::node_ext::macro_call_for_string_token; use syntax::{ ast::{self, IsString}, - AstNode, AstToken, TextRange, TextSize, + TextRange, TextSize, }; pub fn is_format_string(string: &ast::String) -> bool { @@ -14,8 +15,7 @@ pub fn is_format_string(string: &ast::String) -> bool { // This setup lets us correctly highlight the components of `concat!("{}", "bla")` format // strings. It still fails for `concat!("{", "}")`, but that is rare. (|| { - let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?; - let name = macro_call.path()?.segment()?.name_ref()?; + let name = macro_call_for_string_token(string)?.path()?.segment()?.name_ref()?; if !matches!( name.text().as_str(), diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs index b890e2b58d..39710b8f13 100644 --- a/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -2,8 +2,8 @@ use itertools::Itertools; use parser::T; use syntax::{ - ast::{self, HasLoopBody, PathSegmentKind, VisibilityKind}, - AstNode, Preorder, RustLanguage, WalkEvent, + ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind}, + AstNode, AstToken, Preorder, RustLanguage, WalkEvent, }; pub fn expr_as_name_ref(expr: &ast::Expr) -> Option { @@ -457,3 +457,8 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option Option { + let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?; + Some(macro_call) +} diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml index 9b9e21a4dd..e1d146f4ee 100644 --- a/crates/ide-diagnostics/Cargo.toml +++ b/crates/ide-diagnostics/Cargo.toml @@ -11,11 +11,9 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" -itertools = "0.10.3" - - either = "1.7.0" -serde_json = "1.0.82" +itertools = "0.10.5" +serde_json = "1.0.86" profile = { path = "../profile", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" } diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs index 04918891b2..f558b7256a 100644 --- a/crates/ide-diagnostics/src/handlers/inactive_code.rs +++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs @@ -137,6 +137,37 @@ trait Bar { #[cfg_attr(not(never), inline, cfg(no))] fn h() {} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled +"#, + ); + } + + #[test] + fn inactive_fields_and_variants() { + check( + r#" +enum Foo { + #[cfg(a)] Bar, +//^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled + Baz { + #[cfg(a)] baz: String, + //^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled + }, + Qux(#[cfg(a)] String), + //^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled +} + +struct Baz { + #[cfg(a)] baz: String, +//^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled +} + +struct Qux(#[cfg(a)] String); + //^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled + +union FooBar { + #[cfg(a)] baz: u32, +//^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled +} "#, ); } diff --git a/crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs b/crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs new file mode 100644 index 0000000000..085d8d3259 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs @@ -0,0 +1,37 @@ +use hir::InFile; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: incorrect-try-target +// +// This diagnostic is triggered if a question mark operator was used in a context where it is not applicable. +pub(crate) fn incorrect_try_expr( + ctx: &DiagnosticsContext<'_>, + d: &hir::IncorrectTryExpr, +) -> Diagnostic { + Diagnostic::new( + "incorrect-try-target", + format!("the return type of the containing function does not implement `FromResidual`"), + ctx.sema + .diagnostics_display_range(InFile::new(d.expr.file_id, d.expr.value.clone().into())) + .range, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn try_ops_diag() { + check_diagnostics( + r#" +//- minicore: try +fn test() { + core::ops::ControlFlow::::Continue(1.0)?; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: the return type of the containing function does not implement `FromResidual` +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/not_implemented.rs b/crates/ide-diagnostics/src/handlers/not_implemented.rs new file mode 100644 index 0000000000..3bf6a42322 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/not_implemented.rs @@ -0,0 +1,35 @@ +use hir::{db::DefDatabase, HirDisplay}; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: not-implemented +// +// This diagnostic is triggered if a type doesn't implement a necessary trait. +pub(crate) fn not_implemented(ctx: &DiagnosticsContext<'_>, d: &hir::NotImplemented) -> Diagnostic { + Diagnostic::new( + "not-implemented", + format!( + "the trait `{}` is not implemented for `{}`", + ctx.sema.db.trait_data(d.trait_).name, + d.ty.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn missing_try_impl() { + check_diagnostics( + r#" +//- minicore: try +fn main() { + ()?; +} //^^ error: the trait `Try` is not implemented for `()` +"#, + ) + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index ae299f0584..4577072149 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -29,6 +29,7 @@ mod handlers { pub(crate) mod break_outside_of_loop; pub(crate) mod inactive_code; pub(crate) mod incorrect_case; + pub(crate) mod incorrect_try_expr; pub(crate) mod invalid_derive_target; pub(crate) mod macro_error; pub(crate) mod malformed_derive; @@ -36,6 +37,7 @@ mod handlers { pub(crate) mod missing_fields; pub(crate) mod missing_match_arms; pub(crate) mod missing_unsafe; + pub(crate) mod not_implemented; pub(crate) mod no_such_field; pub(crate) mod replace_filter_map_next_with_find_map; pub(crate) mod type_mismatch; @@ -225,12 +227,14 @@ pub fn diagnostics( let d = match diag { AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), + AnyDiagnostic::IncorrectTryExpr(d) => handlers::incorrect_try_expr::incorrect_try_expr(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d), AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d), AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d), AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d), AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d), + AnyDiagnostic::NotImplemented(d) => handlers::not_implemented::not_implemented(&ctx, &d), AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d), AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d), AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d), diff --git a/crates/ide-ssr/Cargo.toml b/crates/ide-ssr/Cargo.toml index 73314e0f33..4baf786c45 100644 --- a/crates/ide-ssr/Cargo.toml +++ b/crates/ide-ssr/Cargo.toml @@ -12,8 +12,7 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" - -itertools = "0.10.3" +itertools = "0.10.5" text-edit = { path = "../text-edit", version = "0.0.0" } parser = { path = "../parser", version = "0.0.0" } diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 0e9771cd2e..712459a7ee 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -13,12 +13,12 @@ doctest = false cov-mark = "2.0.0-pre.1" crossbeam-channel = "0.5.5" either = "1.7.0" -itertools = "0.10.3" +itertools = "0.10.5" tracing = "0.1.35" oorandom = "11.1.3" -pulldown-cmark-to-cmark = "10.0.1" +pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.1", default-features = false } -url = "2.2.2" +url = "2.3.1" dot = "0.1.4" stdx = { path = "../stdx", version = "0.0.0" } diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs index bfbe0db6e4..f994c284c7 100644 --- a/crates/ide/src/annotations.rs +++ b/crates/ide/src/annotations.rs @@ -8,13 +8,15 @@ use ide_db::{ use syntax::{ast::HasName, AstNode, TextRange}; use crate::{ - fn_references::find_all_methods, + annotations::fn_references::find_all_methods, goto_implementation::goto_implementation, references::find_all_refs, runnables::{runnables, Runnable}, NavigationTarget, RunnableKind, }; +mod fn_references; + // Feature: Annotations // // Provides user with annotations above items for looking up references or impl blocks @@ -30,8 +32,8 @@ pub struct Annotation { #[derive(Debug)] pub enum AnnotationKind { Runnable(Runnable), - HasImpls { file_id: FileId, data: Option> }, - HasReferences { file_id: FileId, data: Option> }, + HasImpls { pos: FilePosition, data: Option> }, + HasReferences { pos: FilePosition, data: Option> }, } pub struct AnnotationConfig { @@ -68,13 +70,23 @@ pub(crate) fn annotations( } } + let mk_ranges = |(range, focus): (_, Option<_>)| { + let cmd_target: TextRange = focus.unwrap_or(range); + let annotation_range = match config.location { + AnnotationLocation::AboveName => cmd_target, + AnnotationLocation::AboveWholeItem => range, + }; + let target_pos = FilePosition { file_id, offset: cmd_target.start() }; + (annotation_range, target_pos) + }; + visit_file_defs(&Semantics::new(db), file_id, &mut |def| { let range = match def { Definition::Const(konst) if config.annotate_references => { - konst.source(db).and_then(|node| name_range(db, config, node, file_id)) + konst.source(db).and_then(|node| name_range(db, node, file_id)) } Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => { - trait_.source(db).and_then(|node| name_range(db, config, node, file_id)) + trait_.source(db).and_then(|node| name_range(db, node, file_id)) } Definition::Adt(adt) => match adt { hir::Adt::Enum(enum_) => { @@ -83,27 +95,29 @@ pub(crate) fn annotations( .variants(db) .into_iter() .map(|variant| { - variant - .source(db) - .and_then(|node| name_range(db, config, node, file_id)) + variant.source(db).and_then(|node| name_range(db, node, file_id)) }) .flatten() .for_each(|range| { + let (annotation_range, target_position) = mk_ranges(range); annotations.push(Annotation { - range, - kind: AnnotationKind::HasReferences { file_id, data: None }, + range: annotation_range, + kind: AnnotationKind::HasReferences { + pos: target_position, + data: None, + }, }) }) } if config.annotate_references || config.annotate_impls { - enum_.source(db).and_then(|node| name_range(db, config, node, file_id)) + enum_.source(db).and_then(|node| name_range(db, node, file_id)) } else { None } } _ => { if config.annotate_references || config.annotate_impls { - adt.source(db).and_then(|node| name_range(db, config, node, file_id)) + adt.source(db).and_then(|node| name_range(db, node, file_id)) } else { None } @@ -116,33 +130,32 @@ pub(crate) fn annotations( Some(range) => range, None => return, }; - + let (annotation_range, target_pos) = mk_ranges(range); if config.annotate_impls && !matches!(def, Definition::Const(_)) { - annotations - .push(Annotation { range, kind: AnnotationKind::HasImpls { file_id, data: None } }); + annotations.push(Annotation { + range: annotation_range, + kind: AnnotationKind::HasImpls { pos: target_pos, data: None }, + }); } if config.annotate_references { annotations.push(Annotation { - range, - kind: AnnotationKind::HasReferences { file_id, data: None }, + range: annotation_range, + kind: AnnotationKind::HasReferences { pos: target_pos, data: None }, }); } fn name_range( db: &RootDatabase, - config: &AnnotationConfig, node: InFile, source_file_id: FileId, - ) -> Option { + ) -> Option<(TextRange, Option)> { if let Some(InFile { file_id, value }) = node.original_ast_node(db) { if file_id == source_file_id.into() { - return match config.location { - AnnotationLocation::AboveName => { - value.name().map(|name| name.syntax().text_range()) - } - AnnotationLocation::AboveWholeItem => Some(value.syntax().text_range()), - }; + return Some(( + value.syntax().text_range(), + value.name().map(|name| name.syntax().text_range()), + )); } } None @@ -150,12 +163,13 @@ pub(crate) fn annotations( }); if config.annotate_method_references { - annotations.extend(find_all_methods(db, file_id).into_iter().map( - |FileRange { file_id, range }| Annotation { - range, - kind: AnnotationKind::HasReferences { file_id, data: None }, - }, - )); + annotations.extend(find_all_methods(db, file_id).into_iter().map(|range| { + let (annotation_range, target_range) = mk_ranges(range); + Annotation { + range: annotation_range, + kind: AnnotationKind::HasReferences { pos: target_range, data: None }, + } + })); } annotations @@ -163,18 +177,11 @@ pub(crate) fn annotations( pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation { match annotation.kind { - AnnotationKind::HasImpls { file_id, ref mut data } => { - *data = - goto_implementation(db, FilePosition { file_id, offset: annotation.range.start() }) - .map(|range| range.info); + AnnotationKind::HasImpls { pos, ref mut data } => { + *data = goto_implementation(db, pos).map(|range| range.info); } - AnnotationKind::HasReferences { file_id, ref mut data } => { - *data = find_all_refs( - &Semantics::new(db), - FilePosition { file_id, offset: annotation.range.start() }, - None, - ) - .map(|result| { + AnnotationKind::HasReferences { pos, ref mut data } => { + *data = find_all_refs(&Semantics::new(db), pos, None).map(|result| { result .into_iter() .flat_map(|res| res.references) @@ -268,9 +275,12 @@ fn main() { Annotation { range: 6..10, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 6, + }, data: Some( [ FileRange { @@ -286,9 +296,12 @@ fn main() { Annotation { range: 30..36, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 30, + }, data: Some( [], ), @@ -297,9 +310,12 @@ fn main() { Annotation { range: 53..57, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 53, + }, data: Some( [], ), @@ -344,9 +360,12 @@ fn main() { Annotation { range: 7..11, kind: HasImpls { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, data: Some( [], ), @@ -355,9 +374,12 @@ fn main() { Annotation { range: 7..11, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, data: Some( [ FileRange { @@ -373,9 +395,12 @@ fn main() { Annotation { range: 17..21, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 17, + }, data: Some( [], ), @@ -424,9 +449,12 @@ fn main() { Annotation { range: 7..11, kind: HasImpls { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, data: Some( [ NavigationTarget { @@ -445,9 +473,12 @@ fn main() { Annotation { range: 7..11, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, data: Some( [ FileRange { @@ -469,9 +500,12 @@ fn main() { Annotation { range: 20..31, kind: HasImpls { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 20, + }, data: Some( [ NavigationTarget { @@ -490,9 +524,12 @@ fn main() { Annotation { range: 20..31, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 20, + }, data: Some( [ FileRange { @@ -508,9 +545,12 @@ fn main() { Annotation { range: 69..73, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 69, + }, data: Some( [], ), @@ -551,9 +591,12 @@ fn main() {} Annotation { range: 3..7, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 3, + }, data: Some( [], ), @@ -602,9 +645,12 @@ fn main() { Annotation { range: 7..11, kind: HasImpls { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, data: Some( [ NavigationTarget { @@ -623,9 +669,12 @@ fn main() { Annotation { range: 7..11, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, data: Some( [ FileRange { @@ -647,9 +696,12 @@ fn main() { Annotation { range: 33..44, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 33, + }, data: Some( [ FileRange { @@ -665,9 +717,12 @@ fn main() { Annotation { range: 61..65, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 61, + }, data: Some( [], ), @@ -761,9 +816,12 @@ mod tests { Annotation { range: 3..7, kind: HasReferences { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 3, + }, data: Some( [], ), @@ -821,9 +879,12 @@ struct Foo; Annotation { range: 0..71, kind: HasImpls { - file_id: FileId( - 0, - ), + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 67, + }, data: Some( [], ), @@ -832,10 +893,15 @@ struct Foo; Annotation { range: 0..71, kind: HasReferences { - file_id: FileId( - 0, + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 67, + }, + data: Some( + [], ), - data: None, }, }, ] diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/annotations/fn_references.rs similarity index 61% rename from crates/ide/src/fn_references.rs rename to crates/ide/src/annotations/fn_references.rs index 63fb322cea..0cadf125fe 100644 --- a/crates/ide/src/fn_references.rs +++ b/crates/ide/src/annotations/fn_references.rs @@ -4,30 +4,38 @@ use hir::Semantics; use ide_assists::utils::test_related_attribute; use ide_db::RootDatabase; -use syntax::{ast, ast::HasName, AstNode, SyntaxNode}; +use syntax::{ast, ast::HasName, AstNode, SyntaxNode, TextRange}; -use crate::{FileId, FileRange}; +use crate::FileId; -pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec { +pub(super) fn find_all_methods( + db: &RootDatabase, + file_id: FileId, +) -> Vec<(TextRange, Option)> { let sema = Semantics::new(db); let source_file = sema.parse(file_id); - source_file.syntax().descendants().filter_map(|it| method_range(it, file_id)).collect() + source_file.syntax().descendants().filter_map(|it| method_range(it)).collect() } -fn method_range(item: SyntaxNode, file_id: FileId) -> Option { +fn method_range(item: SyntaxNode) -> Option<(TextRange, Option)> { ast::Fn::cast(item).and_then(|fn_def| { if test_related_attribute(&fn_def).is_some() { None } else { - fn_def.name().map(|name| FileRange { file_id, range: name.syntax().text_range() }) + Some(( + fn_def.syntax().text_range(), + fn_def.name().map(|name| name.syntax().text_range()), + )) } }) } #[cfg(test)] mod tests { + use syntax::TextRange; + use crate::fixture; - use crate::{FileRange, TextSize}; + use crate::TextSize; use std::ops::RangeInclusive; #[test] @@ -42,7 +50,7 @@ mod tests { "#, ); - let refs = analysis.find_all_methods(pos.file_id).unwrap(); + let refs = super::find_all_methods(&analysis.db, pos.file_id); check_result(&refs, &[3..=13, 27..=33, 47..=57]); } @@ -57,7 +65,7 @@ mod tests { "#, ); - let refs = analysis.find_all_methods(pos.file_id).unwrap(); + let refs = super::find_all_methods(&analysis.db, pos.file_id); check_result(&refs, &[19..=22, 35..=38]); } @@ -78,17 +86,18 @@ mod tests { "#, ); - let refs = analysis.find_all_methods(pos.file_id).unwrap(); + let refs = super::find_all_methods(&analysis.db, pos.file_id); check_result(&refs, &[28..=34]); } - fn check_result(refs: &[FileRange], expected: &[RangeInclusive]) { + fn check_result(refs: &[(TextRange, Option)], expected: &[RangeInclusive]) { assert_eq!(refs.len(), expected.len()); - for (i, item) in refs.iter().enumerate() { + for (i, &(full, focus)) in refs.iter().enumerate() { let range = &expected[i]; - assert_eq!(TextSize::from(*range.start()), item.range.start()); - assert_eq!(TextSize::from(*range.end()), item.range.end()); + let item = focus.unwrap_or(full); + assert_eq!(TextSize::from(*range.start()), item.start()); + assert_eq!(TextSize::from(*range.end()), item.end()); } } } diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 92ce26b422..d96827326c 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -232,8 +232,13 @@ pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option TextSize::try_from(comment.prefix().len()).ok(), - ast::String(string) => doc_token.parent_ancestors().find_map(ast::Attr::cast) - .filter(|attr| attr.simple_name().as_deref() == Some("doc")).and_then(|_| string.open_quote_text_range().map(|it| it.len())), + ast::String(string) => { + doc_token.parent_ancestors().find_map(ast::Attr::cast).filter(|attr| attr.simple_name().as_deref() == Some("doc"))?; + if doc_token.parent_ancestors().find_map(ast::MacroCall::cast).filter(|mac| mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()).as_deref() == Some("include_str")).is_some() { + return None; + } + string.open_quote_text_range().map(|it| it.len()) + }, _ => None, } }).map(|prefix_len| DocCommentToken { prefix_len, doc_token: doc_token.clone() }) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 36a648fe4a..d0be1b3f40 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -48,10 +48,14 @@ pub(crate) fn goto_definition( _ => 1, })?; if let Some(doc_comment) = token_as_doc_comment(&original_token) { - return doc_comment.get_definition_with_descend_at(sema, position.offset, |def, _, _| { - let nav = def.try_to_nav(db)?; - Some(RangeInfo::new(original_token.text_range(), vec![nav])) - }); + return doc_comment.get_definition_with_descend_at( + sema, + position.offset, + |def, _, link_range| { + let nav = def.try_to_nav(db)?; + Some(RangeInfo::new(link_range, vec![nav])) + }, + ); } let navs = sema .descend_into_macros(original_token.clone()) @@ -95,6 +99,14 @@ fn try_lookup_include_path( if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") { return None; } + + // Ignore non-built-in macros to account for shadowing + if let Some(it) = sema.resolve_macro_call(¯o_call) { + if !matches!(it.kind(sema.db), hir::MacroKind::BuiltIn) { + return None; + } + } + let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?; let size = sema.db.file_text(file_id).len().try_into().ok()?; Some(NavigationTarget { @@ -156,9 +168,6 @@ mod tests { fn check(ra_fixture: &str) { let (analysis, position, expected) = fixture::annotations(ra_fixture); let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; - if navs.is_empty() { - panic!("unresolved reference") - } let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start()); let navs = navs @@ -1348,6 +1357,10 @@ fn f(e: Enum) { check( r#" //- /main.rs + +#[rustc_builtin_macro] +macro_rules! include_str {} + fn main() { let str = include_str!("foo.txt$0"); } @@ -1357,6 +1370,42 @@ fn main() { "#, ); } + + #[test] + fn goto_doc_include_str() { + check( + r#" +//- /main.rs +#[rustc_builtin_macro] +macro_rules! include_str {} + +#[doc = include_str!("docs.md$0")] +struct Item; + +//- /docs.md +// docs +//^file +"#, + ); + } + + #[test] + fn goto_shadow_include() { + check( + r#" +//- /main.rs +macro_rules! include { + ("included.rs") => {} +} + +include!("included.rs$0"); + +//- /included.rs +// empty +"#, + ); + } + #[cfg(test)] mod goto_impl_of_trait_fn { use super::check; diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 1bdd626f1e..540a115832 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -1373,6 +1373,22 @@ fn main() { ().func$0(); //^^^^ } +"#, + ); + } + + #[test] + fn test_assoc_type_highlighting() { + check( + r#" +trait Trait { + type Output; + // ^^^^^^ +} +impl Trait for () { + type Output$0 = (); + // ^^^^^^ +} "#, ); } diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index c5c50d88dd..d109c07691 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -346,7 +346,16 @@ pub(super) fn definition( Definition::Module(it) => label_and_docs(db, it), Definition::Function(it) => label_and_docs(db, it), Definition::Adt(it) => label_and_docs(db, it), - Definition::Variant(it) => label_and_docs(db, it), + Definition::Variant(it) => label_value_and_docs(db, it, |&it| { + if !it.parent_enum(db).is_data_carrying(db) { + match it.eval(db) { + Ok(x) => Some(format!("{}", x)), + Err(_) => it.value(db).map(|x| format!("{:?}", x)), + } + } else { + None + } + }), Definition::Const(it) => label_value_and_docs(db, it, |it| { let body = it.eval(db); match body { diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 4b8b47783d..5cab017a58 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -698,6 +698,7 @@ fn hover_enum_variant() { check( r#" enum Option { + Some(T) /// The None variant Non$0e } @@ -3527,6 +3528,112 @@ impl Foo {} ); } +#[test] +fn hover_const_eval_variant() { + // show hex for <10 + check( + r#" +#[repr(u8)] +enum E { + /// This is a doc + A$0 = 1 << 3, +} +"#, + expect![[r#" + *A* + + ```rust + test::E + ``` + + ```rust + A = 8 + ``` + + --- + + This is a doc + "#]], + ); + // show hex for >10 + check( + r#" +#[repr(u8)] +enum E { + /// This is a doc + A$0 = (1 << 3) + (1 << 2), +} +"#, + expect![[r#" + *A* + + ```rust + test::E + ``` + + ```rust + A = 12 (0xC) + ``` + + --- + + This is a doc + "#]], + ); + // enums in const eval + check( + r#" +#[repr(u8)] +enum E { + A = 1, + /// This is a doc + B$0 = E::A as u8 + 1, +} +"#, + expect![[r#" + *B* + + ```rust + test::E + ``` + + ```rust + B = 2 + ``` + + --- + + This is a doc + "#]], + ); + // unspecified variant should increment by one + check( + r#" +#[repr(u8)] +enum E { + A = 4, + /// This is a doc + B$0, +} +"#, + expect![[r#" + *B* + + ```rust + test::E + ``` + + ```rust + B = 5 + ``` + + --- + + This is a doc + "#]], + ); +} + #[test] fn hover_const_eval() { // show hex for <10 @@ -3820,6 +3927,35 @@ fn foo() { --- + This is a doc + "#]], + ); + check( + r#" +enum E { + /// This is a doc + A = 3, +} +fn foo(e: E) { + match e { + E::A$0 => (), + _ => () + } +} +"#, + expect![[r#" + *A* + + ```rust + test::E + ``` + + ```rust + A = 3 + ``` + + --- + This is a doc "#]], ); @@ -4777,6 +4913,22 @@ fn foo() -> NotResult<(), Short> { ``` "#]], ); + check_hover_range( + r#" +//- minicore: try +use core::ops::ControlFlow; +fn foo() -> ControlFlow<()> { + $0ControlFlow::Break(())?$0; + ControlFlow::Continue(()) +} +"#, + expect![[r#" + ```text + Try Target Type: ControlFlow<(), {unknown}> + Propagated as: ControlFlow<(), ()> + ``` + "#]], + ); } #[test] @@ -4792,9 +4944,9 @@ fn foo() -> Option<()> { } "#, expect![[r#" - ```rust - as Try>::Output - ```"#]], + ```rust + i32 + ```"#]], ); } diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 93fcd7cad7..34d8bf67a3 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -176,12 +176,6 @@ impl fmt::Debug for InlayHintLabelPart { // * elided lifetimes // * compiler inserted reborrows // -// |=== -// | Editor | Action Name -// -// | VS Code | **rust-analyzer: Toggle inlay hints* -// |=== -// // image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[] pub(crate) fn inlay_hints( db: &RootDatabase, @@ -2030,7 +2024,14 @@ impl Vec { } impl IntoIterator for Vec { - type Item=T; + type Item = T; + type IntoIter = IntoIter; +} + +struct IntoIter {} + +impl Iterator for IntoIter { + type Item = T; } fn main() { diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index c1ef25b592..77fe0dbf55 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -31,7 +31,6 @@ mod highlight_related; mod expand_macro; mod extend_selection; mod file_structure; -mod fn_references; mod folding_ranges; mod goto_declaration; mod goto_definition; @@ -236,7 +235,7 @@ impl Analysis { Env::default(), Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None }, + CrateOrigin::CratesIo { repo: None, name: None }, ); change.change_file(file_id, Some(Arc::new(text))); change.set_crate_graph(crate_graph); @@ -429,11 +428,6 @@ impl Analysis { self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope)) } - /// Finds all methods and free functions for the file. Does not return tests! - pub fn find_all_methods(&self, file_id: FileId) -> Cancellable> { - self.with_db(|db| fn_references::find_all_methods(db, file_id)) - } - /// Returns a short text describing element at position. pub fn hover( &self, diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index 600a526300..852a8fd837 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -253,10 +253,14 @@ pub(crate) fn def_to_moniker( }, kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import }, package_information: { - let name = krate.display_name(db)?.to_string(); - let (repo, version) = match krate.origin(db) { - CrateOrigin::CratesIo { repo } => (repo?, krate.version(db)?), + let (name, repo, version) = match krate.origin(db) { + CrateOrigin::CratesIo { repo, name } => ( + name.unwrap_or(krate.display_name(db)?.canonical_name().to_string()), + repo?, + krate.version(db)?, + ), CrateOrigin::Lang(lang) => ( + krate.display_name(db)?.canonical_name().to_string(), "https://github.com/rust-lang/rust/".to_string(), match lang { LangCrateOrigin::Other => { diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 9395e914c4..e7d0a8be7f 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -87,9 +87,9 @@ fn punctuation( let parent = token.parent(); let parent_kind = parent.as_ref().map_or(EOF, SyntaxNode::kind); match (kind, parent_kind) { - (T![?], _) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow, + (T![?], TRY_EXPR) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow, (T![&], BIN_EXPR) => HlOperator::Bitwise.into(), - (T![&], _) => { + (T![&], REF_EXPR) => { let h = HlTag::Operator(HlOperator::Other).into(); let is_unsafe = parent .and_then(ast::RefExpr::cast) @@ -100,7 +100,9 @@ fn punctuation( h } } - (T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.], _) => HlOperator::Other.into(), + (T![::] | T![->] | T![=>] | T![..] | T![..=] | T![=] | T![@] | T![.], _) => { + HlOperator::Other.into() + } (T![!], MACRO_CALL | MACRO_RULES) => HlPunct::MacroBang.into(), (T![!], NEVER_TYPE) => HlTag::BuiltinType.into(), (T![!], PREFIX_EXPR) => HlOperator::Logical.into(), @@ -129,7 +131,7 @@ fn punctuation( (T![+=] | T![-=] | T![*=] | T![/=] | T![%=], BIN_EXPR) => { Highlight::from(HlOperator::Arithmetic) | HlMod::Mutable } - (T![|] | T![&] | T![!] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(), + (T![|] | T![&] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(), (T![|=] | T![&=] | T![^=] | T![>>=] | T![<<=], BIN_EXPR) => { Highlight::from(HlOperator::Bitwise) | HlMod::Mutable } @@ -137,7 +139,6 @@ fn punctuation( (T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=], BIN_EXPR) => { HlOperator::Comparison.into() } - (_, PREFIX_EXPR | BIN_EXPR | RANGE_EXPR | RANGE_PAT | REST_PAT) => HlOperator::Other.into(), (_, ATTR) => HlTag::AttributeBracket.into(), (kind, _) => match kind { T!['['] | T![']'] => HlPunct::Bracket, diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html index e07fd3925c..9ed65fbc85 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html @@ -48,15 +48,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd impl foo { pub fn is_static() {} - pub fn is_not_static(&self) {} + pub fn is_not_static(&self) {} } trait t { fn t_is_static() {} - fn t_is_not_static(&self) {} + fn t_is_not_static(&self) {} } impl t for foo { pub fn is_static() {} - pub fn is_not_static(&self) {} + pub fn is_not_static(&self) {} } \ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index eef5baea98..18045f1f55 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -125,7 +125,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd /// ```sh /// echo 1 /// ``` - pub fn foo(&self) -> bool { + pub fn foo(&self) -> bool { true } } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html index a97802cbbd..9f2b1926b5 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html @@ -61,11 +61,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } trait Bar { - fn bar(&self) -> i32; + fn bar(&self) -> i32; } impl Bar for Foo { - fn bar(&self) -> i32 { + fn bar(&self) -> i32 { self.x } } @@ -75,11 +75,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd f.baz(self) } - fn qux(&mut self) { + fn qux(&mut self) { self.x = 0; } - fn quop(&self) -> i32 { + fn quop(&self) -> i32 { self.x } } @@ -96,11 +96,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd f.baz(self) } - fn qux(&mut self) { + fn qux(&mut self) { self.x = 0; } - fn quop(&self) -> u32 { + fn quop(&self) -> u32 { self.x } } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html index ced7d22f03..abcd80c280 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html @@ -42,7 +42,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } -
fn fixture(ra_fixture: &str) {}
+
fn fixture(ra_fixture: &str) {}
 
 fn main() {
     fixture(r#"
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
index 2d85fc8c92..f98e0b1cda 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
@@ -45,8 +45,8 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 

 #[derive()]
 struct Foo<'a, 'b, 'c> where 'a: 'a, 'static: 'static {
-    field: &'a (),
-    field2: &'static (),
+    field: &'a (),
+    field2: &'static (),
 }
 impl<'a> Foo<'_, 'a, 'static>
 where
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index c627bc9b09..a626cda3fe 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -62,16 +62,16 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
         () => (
             $crate::panicking::panic("explicit panic")
         ),
-        ($msg:literal $(,)?) => (
+        ($msg:literal $(,)?) => (
             $crate::panicking::panic($msg)
         ),
         // Use `panic_str` instead of `panic_display::<&str>` for non_fmt_panic lint.
-        ($msg:expr $(,)?) => (
+        ($msg:expr $(,)?) => (
             $crate::panicking::panic_str($msg)
         ),
         // Special-case the single-argument case for const_panic.
-        ("{}", $arg:expr $(,)?) => (
-            $crate::panicking::panic_display(&$arg)
+        ("{}", $arg:expr $(,)?) => (
+            $crate::panicking::panic_display(&$arg)
         ),
         ($fmt:expr, $($arg:tt)+) => (
             $crate::panicking::panic_fmt($crate::const_format_args!($fmt, $($arg)+))
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 0716bae751..1992bdc6ae 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -49,7 +49,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 }
 macro_rules! unsafe_deref {
     () => {
-        *(&() as *const ())
+        *(&() as *const ())
     };
 }
 static mut MUT_GLOBAL: Struct = Struct { field: 0 };
@@ -63,7 +63,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 struct Struct { field: i32 }
 impl Struct {
-    unsafe fn unsafe_method(&self) {}
+    unsafe fn unsafe_method(&self) {}
 }
 
 #[repr(packed)]
@@ -78,11 +78,11 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 fn unsafe_trait_bound<T: UnsafeTrait>(_: T) {}
 
 trait DoTheAutoref {
-    fn calls_autoref(&self);
+    fn calls_autoref(&self);
 }
 
 impl DoTheAutoref for u16 {
-    fn calls_autoref(&self) {}
+    fn calls_autoref(&self) {}
 }
 
 fn main() {
diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml
index 5ff3448a19..13cd890103 100644
--- a/crates/mbe/Cargo.toml
+++ b/crates/mbe/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
 [dependencies]
 cov-mark = "2.0.0-pre.1"
 rustc-hash = "1.1.0"
-smallvec = "1.9.0"
+smallvec = "1.10.0"
 tracing = "0.1.35"
 
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
index ac691578d8..9c92bae6a1 100644
--- a/crates/mbe/src/benchmark.rs
+++ b/crates/mbe/src/benchmark.rs
@@ -8,7 +8,7 @@ use syntax::{
 use test_utils::{bench, bench_fixture, skip_slow_tests};
 
 use crate::{
-    parser::{Op, RepeatKind, Separator},
+    parser::{MetaVarKind, Op, RepeatKind, Separator},
     syntax_node_to_token_tree, DeclarativeMacro,
 };
 
@@ -111,35 +111,35 @@ fn invocation_fixtures(rules: &FxHashMap) -> Vec<(Stri
 
     fn collect_from_op(op: &Op, parent: &mut tt::Subtree, seed: &mut usize) {
         return match op {
-            Op::Var { kind, .. } => match kind.as_ref().map(|it| it.as_str()) {
-                Some("ident") => parent.token_trees.push(make_ident("foo")),
-                Some("ty") => parent.token_trees.push(make_ident("Foo")),
-                Some("tt") => parent.token_trees.push(make_ident("foo")),
-                Some("vis") => parent.token_trees.push(make_ident("pub")),
-                Some("pat") => parent.token_trees.push(make_ident("foo")),
-                Some("path") => parent.token_trees.push(make_ident("foo")),
-                Some("literal") => parent.token_trees.push(make_literal("1")),
-                Some("expr") => parent.token_trees.push(make_ident("foo")),
-                Some("lifetime") => {
+            Op::Var { kind, .. } => match kind.as_ref() {
+                Some(MetaVarKind::Ident) => parent.token_trees.push(make_ident("foo")),
+                Some(MetaVarKind::Ty) => parent.token_trees.push(make_ident("Foo")),
+                Some(MetaVarKind::Tt) => parent.token_trees.push(make_ident("foo")),
+                Some(MetaVarKind::Vis) => parent.token_trees.push(make_ident("pub")),
+                Some(MetaVarKind::Pat) => parent.token_trees.push(make_ident("foo")),
+                Some(MetaVarKind::Path) => parent.token_trees.push(make_ident("foo")),
+                Some(MetaVarKind::Literal) => parent.token_trees.push(make_literal("1")),
+                Some(MetaVarKind::Expr) => parent.token_trees.push(make_ident("foo")),
+                Some(MetaVarKind::Lifetime) => {
                     parent.token_trees.push(make_punct('\''));
                     parent.token_trees.push(make_ident("a"));
                 }
-                Some("block") => {
+                Some(MetaVarKind::Block) => {
                     parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None))
                 }
-                Some("item") => {
+                Some(MetaVarKind::Item) => {
                     parent.token_trees.push(make_ident("fn"));
                     parent.token_trees.push(make_ident("foo"));
                     parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None));
                     parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None));
                 }
-                Some("meta") => {
+                Some(MetaVarKind::Meta) => {
                     parent.token_trees.push(make_ident("foo"));
                     parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None));
                 }
 
                 None => (),
-                Some(kind) => panic!("Unhandled kind {}", kind),
+                Some(kind) => panic!("Unhandled kind {:?}", kind),
             },
             Op::Leaf(leaf) => parent.token_trees.push(leaf.clone().into()),
             Op::Repeat { tokens, kind, separator } => {
diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs
index 1e1bfa5505..100ec6bfb9 100644
--- a/crates/mbe/src/expander.rs
+++ b/crates/mbe/src/expander.rs
@@ -8,7 +8,7 @@ mod transcriber;
 use rustc_hash::FxHashMap;
 use syntax::SmolStr;
 
-use crate::{ExpandError, ExpandResult};
+use crate::{parser::MetaVarKind, ExpandError, ExpandResult};
 
 pub(crate) fn expand_rules(
     rules: &[crate::Rule],
@@ -104,6 +104,7 @@ enum Binding {
     Fragment(Fragment),
     Nested(Vec),
     Empty,
+    Missing(MetaVarKind),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index 139a8cb8cb..3f656df25f 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -66,7 +66,7 @@ use syntax::SmolStr;
 
 use crate::{
     expander::{Binding, Bindings, ExpandResult, Fragment},
-    parser::{Op, RepeatKind, Separator},
+    parser::{MetaVarKind, Op, RepeatKind, Separator},
     tt_iter::TtIter,
     ExpandError, MetaTemplate,
 };
@@ -119,6 +119,7 @@ pub(super) fn match_(pattern: &MetaTemplate, input: &tt::Subtree) -> Match {
             .map(|it| match it {
                 Binding::Fragment(_) => 1,
                 Binding::Empty => 1,
+                Binding::Missing(_) => 1,
                 Binding::Nested(it) => count(it.iter()),
             })
             .sum()
@@ -130,6 +131,7 @@ enum BindingKind {
     Empty(SmolStr),
     Optional(SmolStr),
     Fragment(SmolStr, Fragment),
+    Missing(SmolStr, MetaVarKind),
     Nested(usize, usize),
 }
 
@@ -190,6 +192,10 @@ impl BindingsBuilder {
             .push(LinkNode::Node(Rc::new(BindingKind::Fragment(var.clone(), fragment))));
     }
 
+    fn push_missing(&mut self, idx: &mut BindingsIdx, var: &SmolStr, kind: MetaVarKind) {
+        self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Missing(var.clone(), kind))));
+    }
+
     fn push_nested(&mut self, parent: &mut BindingsIdx, child: &BindingsIdx) {
         let BindingsIdx(idx, nidx) = self.copy(child);
         self.nodes[parent.0].push(LinkNode::Node(Rc::new(BindingKind::Nested(idx, nidx))));
@@ -222,6 +228,9 @@ impl BindingsBuilder {
                 BindingKind::Fragment(name, fragment) => {
                     bindings.inner.insert(name.clone(), Binding::Fragment(fragment.clone()));
                 }
+                BindingKind::Missing(name, kind) => {
+                    bindings.inner.insert(name.clone(), Binding::Missing(*kind));
+                }
                 BindingKind::Nested(idx, nested_idx) => {
                     let mut nested_nodes = Vec::new();
                     self.collect_nested(*idx, *nested_idx, &mut nested_nodes);
@@ -458,9 +467,9 @@ fn match_loop_inner<'t>(
                 }
             }
             OpDelimited::Op(Op::Var { kind, name, .. }) => {
-                if let Some(kind) = kind {
+                if let &Some(kind) = kind {
                     let mut fork = src.clone();
-                    let match_res = match_meta_var(kind.as_str(), &mut fork);
+                    let match_res = match_meta_var(kind, &mut fork);
                     match match_res.err {
                         None => {
                             // Some meta variables are optional (e.g. vis)
@@ -475,8 +484,15 @@ fn match_loop_inner<'t>(
                         }
                         Some(err) => {
                             res.add_err(err);
-                            if let Some(fragment) = match_res.value {
-                                bindings_builder.push_fragment(&mut item.bindings, name, fragment);
+                            match match_res.value {
+                                Some(fragment) => bindings_builder.push_fragment(
+                                    &mut item.bindings,
+                                    name,
+                                    fragment,
+                                ),
+                                None => {
+                                    bindings_builder.push_missing(&mut item.bindings, name, kind)
+                                }
                             }
                             item.is_error = true;
                             error_items.push(item);
@@ -668,20 +684,20 @@ fn match_leaf(lhs: &tt::Leaf, src: &mut TtIter<'_>) -> Result<(), ExpandError> {
     }
 }
 
-fn match_meta_var(kind: &str, input: &mut TtIter<'_>) -> ExpandResult> {
+fn match_meta_var(kind: MetaVarKind, input: &mut TtIter<'_>) -> ExpandResult> {
     let fragment = match kind {
-        "path" => parser::PrefixEntryPoint::Path,
-        "ty" => parser::PrefixEntryPoint::Ty,
+        MetaVarKind::Path => parser::PrefixEntryPoint::Path,
+        MetaVarKind::Ty => parser::PrefixEntryPoint::Ty,
         // FIXME: These two should actually behave differently depending on the edition.
         //
         // https://doc.rust-lang.org/edition-guide/rust-2021/or-patterns-macro-rules.html
-        "pat" | "pat_param" => parser::PrefixEntryPoint::Pat,
-        "stmt" => parser::PrefixEntryPoint::Stmt,
-        "block" => parser::PrefixEntryPoint::Block,
-        "meta" => parser::PrefixEntryPoint::MetaItem,
-        "item" => parser::PrefixEntryPoint::Item,
-        "vis" => parser::PrefixEntryPoint::Vis,
-        "expr" => {
+        MetaVarKind::Pat | MetaVarKind::PatParam => parser::PrefixEntryPoint::Pat,
+        MetaVarKind::Stmt => parser::PrefixEntryPoint::Stmt,
+        MetaVarKind::Block => parser::PrefixEntryPoint::Block,
+        MetaVarKind::Meta => parser::PrefixEntryPoint::MetaItem,
+        MetaVarKind::Item => parser::PrefixEntryPoint::Item,
+        MetaVarKind::Vis => parser::PrefixEntryPoint::Vis,
+        MetaVarKind::Expr => {
             // `expr` should not match underscores.
             // HACK: Macro expansion should not be done using "rollback and try another alternative".
             // rustc [explicitly checks the next token][0].
@@ -698,17 +714,17 @@ fn match_meta_var(kind: &str, input: &mut TtIter<'_>) -> ExpandResult {
             let tt_result = match kind {
-                "ident" => input
+                MetaVarKind::Ident => input
                     .expect_ident()
                     .map(|ident| tt::Leaf::from(ident.clone()).into())
                     .map_err(|()| ExpandError::binding_error("expected ident")),
-                "tt" => input
+                MetaVarKind::Tt => input
                     .expect_tt()
                     .map_err(|()| ExpandError::binding_error("expected token tree")),
-                "lifetime" => input
+                MetaVarKind::Lifetime => input
                     .expect_lifetime()
                     .map_err(|()| ExpandError::binding_error("expected lifetime")),
-                "literal" => {
+                MetaVarKind::Literal => {
                     let neg = input.eat_char('-');
                     input
                         .expect_literal()
diff --git a/crates/mbe/src/expander/transcriber.rs b/crates/mbe/src/expander/transcriber.rs
index 7bcc84740f..cbb59ab8e6 100644
--- a/crates/mbe/src/expander/transcriber.rs
+++ b/crates/mbe/src/expander/transcriber.rs
@@ -6,7 +6,7 @@ use tt::{Delimiter, Subtree};
 
 use crate::{
     expander::{Binding, Bindings, Fragment},
-    parser::{Op, RepeatKind, Separator},
+    parser::{MetaVarKind, Op, RepeatKind, Separator},
     ExpandError, ExpandResult, MetaTemplate,
 };
 
@@ -15,7 +15,7 @@ impl Bindings {
         self.inner.contains_key(name)
     }
 
-    fn get(&self, name: &str, nesting: &mut [NestingState]) -> Result<&Fragment, ExpandError> {
+    fn get(&self, name: &str, nesting: &mut [NestingState]) -> Result {
         macro_rules! binding_err {
             ($($arg:tt)*) => { ExpandError::binding_error(format!($($arg)*)) };
         }
@@ -26,6 +26,7 @@ impl Bindings {
             nesting_state.hit = true;
             b = match b {
                 Binding::Fragment(_) => break,
+                Binding::Missing(_) => break,
                 Binding::Nested(bs) => bs.get(nesting_state.idx).ok_or_else(|| {
                     nesting_state.at_end = true;
                     binding_err!("could not find nested binding `{name}`")
@@ -37,7 +38,55 @@ impl Bindings {
             };
         }
         match b {
-            Binding::Fragment(it) => Ok(it),
+            Binding::Fragment(it) => Ok(it.clone()),
+            // emit some reasonable default expansion for missing bindings,
+            // this gives better recovery than emitting the `$fragment-name` verbatim
+            Binding::Missing(it) => Ok(match it {
+                MetaVarKind::Stmt => {
+                    Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
+                        id: tt::TokenId::unspecified(),
+                        char: ';',
+                        spacing: tt::Spacing::Alone,
+                    })))
+                }
+                MetaVarKind::Block => Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree {
+                    delimiter: Some(tt::Delimiter {
+                        id: tt::TokenId::unspecified(),
+                        kind: tt::DelimiterKind::Brace,
+                    }),
+                    token_trees: vec![],
+                })),
+                // FIXME: Meta and Item should get proper defaults
+                MetaVarKind::Meta | MetaVarKind::Item | MetaVarKind::Tt | MetaVarKind::Vis => {
+                    Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree {
+                        delimiter: None,
+                        token_trees: vec![],
+                    }))
+                }
+                MetaVarKind::Path
+                | MetaVarKind::Ty
+                | MetaVarKind::Pat
+                | MetaVarKind::PatParam
+                | MetaVarKind::Expr
+                | MetaVarKind::Ident => {
+                    Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
+                        text: SmolStr::new_inline("missing"),
+                        id: tt::TokenId::unspecified(),
+                    })))
+                }
+                MetaVarKind::Lifetime => {
+                    Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
+                        text: SmolStr::new_inline("'missing"),
+                        id: tt::TokenId::unspecified(),
+                    })))
+                }
+                MetaVarKind::Literal => {
+                    Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
+                        text: SmolStr::new_inline("\"missing\""),
+                        id: tt::TokenId::unspecified(),
+                    })))
+                }
+            }),
             Binding::Nested(_) => {
                 Err(binding_err!("expected simple binding, found nested binding `{name}`"))
             }
@@ -157,7 +206,7 @@ fn expand_var(ctx: &mut ExpandCtx<'_>, v: &SmolStr, id: tt::TokenId) -> ExpandRe
     } else {
         ctx.bindings.get(v, &mut ctx.nesting).map_or_else(
             |e| ExpandResult { value: Fragment::Tokens(tt::TokenTree::empty()), err: Some(e) },
-            |b| ExpandResult::ok(b.clone()),
+            |it| ExpandResult::ok(it),
         )
     }
 }
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index 79da84f4a0..c4f0fa20d6 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -21,7 +21,7 @@ mod token_map;
 use std::fmt;
 
 use crate::{
-    parser::{MetaTemplate, Op},
+    parser::{MetaTemplate, MetaVarKind, Op},
     tt_iter::TtIter,
 };
 
@@ -291,9 +291,9 @@ fn validate(pattern: &MetaTemplate) -> Result<(), ParseError> {
                 // Checks that no repetition which could match an empty token
                 // https://github.com/rust-lang/rust/blob/a58b1ed44f5e06976de2bdc4d7dc81c36a96934f/src/librustc_expand/mbe/macro_rules.rs#L558
                 let lsh_is_empty_seq = separator.is_none() && subtree.iter().all(|child_op| {
-                    match child_op {
+                    match *child_op {
                         // vis is optional
-                        Op::Var { kind: Some(kind), .. } => kind == "vis",
+                        Op::Var { kind: Some(kind), .. } => kind == MetaVarKind::Vis,
                         Op::Repeat {
                             kind: parser::RepeatKind::ZeroOrMore | parser::RepeatKind::ZeroOrOne,
                             ..
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index acb4be5846..351c359b73 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -50,7 +50,7 @@ impl MetaTemplate {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum Op {
-    Var { name: SmolStr, kind: Option, id: tt::TokenId },
+    Var { name: SmolStr, kind: Option, id: tt::TokenId },
     Ignore { name: SmolStr, id: tt::TokenId },
     Index { depth: u32 },
     Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option },
@@ -65,6 +65,24 @@ pub(crate) enum RepeatKind {
     ZeroOrOne,
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum MetaVarKind {
+    Path,
+    Ty,
+    Pat,
+    PatParam,
+    Stmt,
+    Block,
+    Meta,
+    Item,
+    Vis,
+    Expr,
+    Ident,
+    Tt,
+    Lifetime,
+    Literal,
+}
+
 #[derive(Clone, Debug, Eq)]
 pub(crate) enum Separator {
     Literal(tt::Literal),
@@ -179,13 +197,30 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
     Ok(res)
 }
 
-fn eat_fragment_kind(src: &mut TtIter<'_>, mode: Mode) -> Result, ParseError> {
+fn eat_fragment_kind(src: &mut TtIter<'_>, mode: Mode) -> Result, ParseError> {
     if let Mode::Pattern = mode {
         src.expect_char(':').map_err(|()| ParseError::unexpected("missing fragment specifier"))?;
         let ident = src
             .expect_ident()
             .map_err(|()| ParseError::unexpected("missing fragment specifier"))?;
-        return Ok(Some(ident.text.clone()));
+        let kind = match ident.text.as_str() {
+            "path" => MetaVarKind::Path,
+            "ty" => MetaVarKind::Ty,
+            "pat" => MetaVarKind::Pat,
+            "pat_param" => MetaVarKind::PatParam,
+            "stmt" => MetaVarKind::Stmt,
+            "block" => MetaVarKind::Block,
+            "meta" => MetaVarKind::Meta,
+            "item" => MetaVarKind::Item,
+            "vis" => MetaVarKind::Vis,
+            "expr" => MetaVarKind::Expr,
+            "ident" => MetaVarKind::Ident,
+            "tt" => MetaVarKind::Tt,
+            "lifetime" => MetaVarKind::Lifetime,
+            "literal" => MetaVarKind::Literal,
+            _ => return Ok(None),
+        };
+        return Ok(Some(kind));
     };
     Ok(None)
 }
diff --git a/crates/proc-macro-api/Cargo.toml b/crates/proc-macro-api/Cargo.toml
index 85a1c13fe7..54879c1870 100644
--- a/crates/proc-macro-api/Cargo.toml
+++ b/crates/proc-macro-api/Cargo.toml
@@ -19,7 +19,7 @@ object = { version = "0.29.0", default-features = false, features = [
 ] }
 serde = { version = "1.0.137", features = ["derive"] }
 serde_json = { version = "1.0.81", features = ["unbounded_depth"] }
-tracing = "0.1.35"
+tracing = "0.1.37"
 memmap2 = "0.5.4"
 snap = "1.0.5"
 
diff --git a/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs b/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs
index 44712f4191..243972b049 100644
--- a/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs
+++ b/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs
@@ -11,6 +11,8 @@ use proc_macro_api::ProcMacroKind;
 
 use super::PanicMessage;
 
+pub use ra_server::TokenStream;
+
 pub(crate) struct Abi {
     exported_macros: Vec,
 }
diff --git a/crates/proc-macro-srv/src/abis/mod.rs b/crates/proc-macro-srv/src/abis/mod.rs
index f7d3a30919..2f854bc159 100644
--- a/crates/proc-macro-srv/src/abis/mod.rs
+++ b/crates/proc-macro-srv/src/abis/mod.rs
@@ -32,8 +32,8 @@ mod abi_sysroot;
 include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
 
 // Used by `test/utils.rs`
-#[cfg(test)]
-pub(crate) use abi_1_63::TokenStream as TestTokenStream;
+#[cfg(all(test, feature = "sysroot-abi"))]
+pub(crate) use abi_sysroot::TokenStream as TestTokenStream;
 
 use super::dylib::LoadProcMacroDylibError;
 pub(crate) use abi_1_58::Abi as Abi_1_58;
@@ -144,3 +144,10 @@ impl Abi {
         }
     }
 }
+
+#[test]
+fn test_version_check() {
+    let path = paths::AbsPathBuf::assert(crate::proc_macro_test_dylib_path());
+    let info = proc_macro_api::read_dylib_info(&path).unwrap();
+    assert!(info.version.1 >= 50);
+}
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index 3679bfc43c..72a2dfe72d 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -20,6 +20,8 @@
 mod dylib;
 mod abis;
 
+pub mod cli;
+
 use std::{
     collections::{hash_map::Entry, HashMap},
     env,
@@ -149,7 +151,10 @@ impl EnvSnapshot {
     }
 }
 
-pub mod cli;
+#[cfg(all(feature = "sysroot-abi", test))]
+mod tests;
 
 #[cfg(test)]
-mod tests;
+pub fn proc_macro_test_dylib_path() -> std::path::PathBuf {
+    proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
+}
diff --git a/crates/proc-macro-srv/src/tests/mod.rs b/crates/proc-macro-srv/src/tests/mod.rs
index 6339d56d01..b46cdddcf6 100644
--- a/crates/proc-macro-srv/src/tests/mod.rs
+++ b/crates/proc-macro-srv/src/tests/mod.rs
@@ -2,10 +2,10 @@
 
 #[macro_use]
 mod utils;
-use expect_test::expect;
-use paths::AbsPathBuf;
 use utils::*;
 
+use expect_test::expect;
+
 #[test]
 fn test_derive_empty() {
     assert_expand("DeriveEmpty", r#"struct S;"#, expect![[r#"SUBTREE $"#]]);
@@ -157,10 +157,3 @@ fn list_test_macros() {
         DeriveError [CustomDerive]"#]]
     .assert_eq(&res);
 }
-
-#[test]
-fn test_version_check() {
-    let path = AbsPathBuf::assert(fixtures::proc_macro_test_dylib_path());
-    let info = proc_macro_api::read_dylib_info(&path).unwrap();
-    assert!(info.version.1 >= 50);
-}
diff --git a/crates/proc-macro-srv/src/tests/utils.rs b/crates/proc-macro-srv/src/tests/utils.rs
index f881fe8684..44b1b6588d 100644
--- a/crates/proc-macro-srv/src/tests/utils.rs
+++ b/crates/proc-macro-srv/src/tests/utils.rs
@@ -1,15 +1,9 @@
 //! utils used in proc-macro tests
 
-use crate::dylib;
-use crate::ProcMacroSrv;
 use expect_test::Expect;
 use std::str::FromStr;
 
-pub mod fixtures {
-    pub fn proc_macro_test_dylib_path() -> std::path::PathBuf {
-        proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
-    }
-}
+use crate::{dylib, proc_macro_test_dylib_path, ProcMacroSrv};
 
 fn parse_string(code: &str) -> Option {
     // This is a bit strange. We need to parse a string into a token stream into
@@ -30,7 +24,7 @@ pub fn assert_expand_attr(macro_name: &str, ra_fixture: &str, attr_args: &str, e
 }
 
 fn assert_expand_impl(macro_name: &str, input: &str, attr: Option<&str>, expect: Expect) {
-    let path = fixtures::proc_macro_test_dylib_path();
+    let path = proc_macro_test_dylib_path();
     let expander = dylib::Expander::new(&path).unwrap();
     let fixture = parse_string(input).unwrap();
     let attr = attr.map(|attr| parse_string(attr).unwrap().into_subtree());
@@ -40,7 +34,7 @@ fn assert_expand_impl(macro_name: &str, input: &str, attr: Option<&str>, expect:
 }
 
 pub(crate) fn list() -> Vec {
-    let dylib_path = fixtures::proc_macro_test_dylib_path();
+    let dylib_path = proc_macro_test_dylib_path();
     let mut srv = ProcMacroSrv::default();
     let res = srv.list_macros(&dylib_path).unwrap();
     res.into_iter().map(|(name, kind)| format!("{} [{:?}]", name, kind)).collect()
diff --git a/crates/profile/Cargo.toml b/crates/profile/Cargo.toml
index 0b78a45a24..5697aea964 100644
--- a/crates/profile/Cargo.toml
+++ b/crates/profile/Cargo.toml
@@ -10,9 +10,9 @@ rust-version = "1.57"
 doctest = false
 
 [dependencies]
-once_cell = "1.12.0"
+once_cell = "1.15.0"
 cfg-if = "1.0.0"
-libc = "0.2.126"
+libc = "0.2.135"
 la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
 countme = { version = "3.0.1", features = ["enable"] }
 jemalloc-ctl = { version = "0.5.0", package = "tikv-jemalloc-ctl", optional = true }
diff --git a/crates/project-model/Cargo.toml b/crates/project-model/Cargo.toml
index bc75d6faa3..6fd7c3166f 100644
--- a/crates/project-model/Cargo.toml
+++ b/crates/project-model/Cargo.toml
@@ -13,10 +13,10 @@ doctest = false
 tracing = "0.1.35"
 rustc-hash = "1.1.0"
 cargo_metadata = "0.15.0"
-semver = "1.0.10"
+semver = "1.0.14"
 serde = { version = "1.0.137", features = ["derive"] }
-serde_json = "1.0.81"
-anyhow = "1.0.57"
+serde_json = "1.0.86"
+anyhow = "1.0.62"
 expect-test = "1.4.0"
 la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
 
diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs
index 837ea01619..d9f09c0349 100644
--- a/crates/project-model/src/build_scripts.rs
+++ b/crates/project-model/src/build_scripts.rs
@@ -15,7 +15,7 @@ use rustc_hash::FxHashMap;
 use semver::Version;
 use serde::Deserialize;
 
-use crate::{cfg_flag::CfgFlag, CargoConfig, CargoWorkspace, Package};
+use crate::{cfg_flag::CfgFlag, CargoConfig, CargoFeatures, CargoWorkspace, Package};
 
 #[derive(Debug, Default, Clone, PartialEq, Eq)]
 pub struct WorkspaceBuildScripts {
@@ -49,7 +49,6 @@ impl WorkspaceBuildScripts {
 
         let mut cmd = Command::new(toolchain::cargo());
         cmd.envs(&config.extra_env);
-
         cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);
 
         // --all-targets includes tests, benches and examples in addition to the
@@ -61,15 +60,18 @@ impl WorkspaceBuildScripts {
             cmd.args(&["--target", target]);
         }
 
-        if config.all_features {
-            cmd.arg("--all-features");
-        } else {
-            if config.no_default_features {
-                cmd.arg("--no-default-features");
+        match &config.features {
+            CargoFeatures::All => {
+                cmd.arg("--all-features");
             }
-            if !config.features.is_empty() {
-                cmd.arg("--features");
-                cmd.arg(config.features.join(" "));
+            CargoFeatures::Selected { features, no_default_features } => {
+                if *no_default_features {
+                    cmd.arg("--no-default-features");
+                }
+                if !features.is_empty() {
+                    cmd.arg("--features");
+                    cmd.arg(features.join(" "));
+                }
             }
         }
 
@@ -152,6 +154,8 @@ impl WorkspaceBuildScripts {
                             Some(&it) => it,
                             None => return,
                         };
+                        progress(format!("running build-script: {}", workspace[package].name));
+
                         let cfgs = {
                             let mut acc = Vec::new();
                             for cfg in message.cfgs {
@@ -187,7 +191,7 @@ impl WorkspaceBuildScripts {
                             None => return,
                         };
 
-                        progress(format!("metadata {}", message.target.name));
+                        progress(format!("building proc-macros: {}", message.target.name));
 
                         if message.target.kind.iter().any(|k| k == "proc-macro") {
                             // Skip rmeta file
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 736d80041b..8e690f1125 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -71,35 +71,40 @@ impl Default for UnsetTestCrates {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum CargoFeatures {
+    All,
+    Selected {
+        /// List of features to activate.
+        features: Vec,
+        /// Do not activate the `default` feature.
+        no_default_features: bool,
+    },
+}
+
+impl Default for CargoFeatures {
+    fn default() -> Self {
+        CargoFeatures::Selected { features: vec![], no_default_features: false }
+    }
+}
+
 #[derive(Default, Clone, Debug, PartialEq, Eq)]
 pub struct CargoConfig {
-    /// Do not activate the `default` feature.
-    pub no_default_features: bool,
-
-    /// Activate all available features
-    pub all_features: bool,
-
     /// List of features to activate.
-    /// This will be ignored if `cargo_all_features` is true.
-    pub features: Vec,
-
+    pub features: CargoFeatures,
     /// rustc target
     pub target: Option,
-
-    /// Don't load sysroot crates (`std`, `core` & friends). Might be useful
-    /// when debugging isolated issues.
-    pub no_sysroot: bool,
-
+    /// Sysroot loading behavior
+    pub sysroot: Option,
     /// rustc private crate source
     pub rustc_source: Option,
-
     /// crates to disable `#[cfg(test)]` on
     pub unset_test_crates: UnsetTestCrates,
-
+    /// Invoke `cargo check` through the RUSTC_WRAPPER.
     pub wrap_rustc_in_build_scripts: bool,
-
+    /// The command to run instead of `cargo check` for building build scripts.
     pub run_build_script_command: Option>,
-
+    /// Extra env vars to set when invoking the cargo command
     pub extra_env: FxHashMap,
 }
 
@@ -143,7 +148,7 @@ pub struct PackageData {
     pub targets: Vec,
     /// Does this package come from the local filesystem (and is editable)?
     pub is_local: bool,
-    // Whether this package is a member of the workspace
+    /// Whether this package is a member of the workspace
     pub is_member: bool,
     /// List of packages this package depends on
     pub dependencies: Vec,
@@ -249,8 +254,8 @@ impl TargetKind {
     }
 }
 
+// Deserialize helper for the cargo metadata
 #[derive(Deserialize, Default)]
-// Deserialise helper for the cargo metadata
 struct PackageMetadata {
     #[serde(rename = "rust-analyzer")]
     rust_analyzer: Option,
@@ -266,22 +271,25 @@ impl CargoWorkspace {
         let target = config
             .target
             .clone()
-            .or_else(|| cargo_config_build_target(cargo_toml, config))
-            .or_else(|| rustc_discover_host_triple(cargo_toml, config));
+            .or_else(|| cargo_config_build_target(cargo_toml, &config.extra_env))
+            .or_else(|| rustc_discover_host_triple(cargo_toml, &config.extra_env));
 
         let mut meta = MetadataCommand::new();
         meta.cargo_path(toolchain::cargo());
         meta.manifest_path(cargo_toml.to_path_buf());
-        if config.all_features {
-            meta.features(CargoOpt::AllFeatures);
-        } else {
-            if config.no_default_features {
-                // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
-                // https://github.com/oli-obk/cargo_metadata/issues/79
-                meta.features(CargoOpt::NoDefaultFeatures);
+        match &config.features {
+            CargoFeatures::All => {
+                meta.features(CargoOpt::AllFeatures);
             }
-            if !config.features.is_empty() {
-                meta.features(CargoOpt::SomeFeatures(config.features.clone()));
+            CargoFeatures::Selected { features, no_default_features } => {
+                if *no_default_features {
+                    // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
+                    // https://github.com/oli-obk/cargo_metadata/issues/79
+                    meta.features(CargoOpt::NoDefaultFeatures);
+                }
+                if !features.is_empty() {
+                    meta.features(CargoOpt::SomeFeatures(features.clone()));
+                }
             }
         }
         meta.current_dir(current_dir.as_os_str());
@@ -295,12 +303,9 @@ impl CargoWorkspace {
         // unclear whether cargo itself supports it.
         progress("metadata".to_string());
 
-        fn exec_with_env(
-            command: &cargo_metadata::MetadataCommand,
-            extra_env: &FxHashMap,
-        ) -> Result {
-            let mut command = command.cargo_command();
-            command.envs(extra_env);
+        (|| -> Result {
+            let mut command = meta.cargo_command();
+            command.envs(&config.extra_env);
             let output = command.output()?;
             if !output.status.success() {
                 return Err(cargo_metadata::Error::CargoMetadata {
@@ -312,12 +317,8 @@ impl CargoWorkspace {
                 .find(|line| line.starts_with('{'))
                 .ok_or(cargo_metadata::Error::NoJson)?;
             cargo_metadata::MetadataCommand::parse(stdout)
-        }
-
-        let meta = exec_with_env(&meta, &config.extra_env)
-            .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))?;
-
-        Ok(meta)
+        })()
+        .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))
     }
 
     pub fn new(mut meta: cargo_metadata::Metadata) -> CargoWorkspace {
@@ -386,32 +387,14 @@ impl CargoWorkspace {
         }
         let resolve = meta.resolve.expect("metadata executed with deps");
         for mut node in resolve.nodes {
-            let source = match pkg_by_id.get(&node.id) {
-                Some(&src) => src,
-                // FIXME: replace this and a similar branch below with `.unwrap`, once
-                // https://github.com/rust-lang/cargo/issues/7841
-                // is fixed and hits stable (around 1.43-is probably?).
-                None => {
-                    tracing::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
-                    continue;
-                }
-            };
+            let &source = pkg_by_id.get(&node.id).unwrap();
             node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg));
-            for (dep_node, kind) in node
+            let dependencies = node
                 .deps
                 .iter()
-                .flat_map(|dep| DepKind::iter(&dep.dep_kinds).map(move |kind| (dep, kind)))
-            {
-                let pkg = match pkg_by_id.get(&dep_node.pkg) {
-                    Some(&pkg) => pkg,
-                    None => {
-                        tracing::error!(
-                            "Dep node id do not match in cargo metadata, ignoring {}",
-                            dep_node.pkg
-                        );
-                        continue;
-                    }
-                };
+                .flat_map(|dep| DepKind::iter(&dep.dep_kinds).map(move |kind| (dep, kind)));
+            for (dep_node, kind) in dependencies {
+                let &pkg = pkg_by_id.get(&dep_node.pkg).unwrap();
                 let dep = PackageDependency { name: dep_node.name.clone(), pkg, kind };
                 packages[source].dependencies.push(dep);
             }
@@ -456,10 +439,7 @@ impl CargoWorkspace {
                     found = true
                 }
                 self[pkg].dependencies.iter().find_map(|dep| {
-                    if &self[dep.pkg].manifest == manifest_path {
-                        return Some(self[pkg].manifest.clone());
-                    }
-                    None
+                    (&self[dep.pkg].manifest == manifest_path).then(|| self[pkg].manifest.clone())
                 })
             })
             .collect::>();
@@ -485,9 +465,12 @@ impl CargoWorkspace {
     }
 }
 
-fn rustc_discover_host_triple(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option {
+fn rustc_discover_host_triple(
+    cargo_toml: &ManifestPath,
+    extra_env: &FxHashMap,
+) -> Option {
     let mut rustc = Command::new(toolchain::rustc());
-    rustc.envs(&config.extra_env);
+    rustc.envs(extra_env);
     rustc.current_dir(cargo_toml.parent()).arg("-vV");
     tracing::debug!("Discovering host platform by {:?}", rustc);
     match utf8_stdout(rustc) {
@@ -509,9 +492,12 @@ fn rustc_discover_host_triple(cargo_toml: &ManifestPath, config: &CargoConfig) -
     }
 }
 
-fn cargo_config_build_target(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option {
+fn cargo_config_build_target(
+    cargo_toml: &ManifestPath,
+    extra_env: &FxHashMap,
+) -> Option {
     let mut cargo_config = Command::new(toolchain::cargo());
-    cargo_config.envs(&config.extra_env);
+    cargo_config.envs(extra_env);
     cargo_config
         .current_dir(cargo_toml.parent())
         .args(&["-Z", "unstable-options", "config", "get", "build.target"])
diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs
index b81b7432f6..ce78ce8569 100644
--- a/crates/project-model/src/lib.rs
+++ b/crates/project-model/src/lib.rs
@@ -42,8 +42,8 @@ use rustc_hash::FxHashSet;
 pub use crate::{
     build_scripts::WorkspaceBuildScripts,
     cargo_workspace::{
-        CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustcSource, Target,
-        TargetData, TargetKind, UnsetTestCrates,
+        CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency,
+        RustcSource, Target, TargetData, TargetKind, UnsetTestCrates,
     },
     manifest_path::ManifestPath,
     project_json::{ProjectJson, ProjectJsonData},
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
index 63d1d0ace9..5133a14d53 100644
--- a/crates/project-model/src/project_json.rs
+++ b/crates/project-model/src/project_json.rs
@@ -110,14 +110,17 @@ impl ProjectJson {
                 .collect::>(),
         }
     }
+
     /// Returns the number of crates in the project.
     pub fn n_crates(&self) -> usize {
         self.crates.len()
     }
+
     /// Returns an iterator over the crates in the project.
     pub fn crates(&self) -> impl Iterator + '_ {
         self.crates.iter().enumerate().map(|(idx, krate)| (CrateId(idx as u32), krate))
     }
+
     /// Returns the path to the project's root folder.
     pub fn path(&self) -> &AbsPath {
         &self.project_root
diff --git a/crates/project-model/src/rustc_cfg.rs b/crates/project-model/src/rustc_cfg.rs
index 486cb143b8..3231361836 100644
--- a/crates/project-model/src/rustc_cfg.rs
+++ b/crates/project-model/src/rustc_cfg.rs
@@ -3,13 +3,14 @@
 use std::process::Command;
 
 use anyhow::Result;
+use rustc_hash::FxHashMap;
 
-use crate::{cfg_flag::CfgFlag, utf8_stdout, CargoConfig, ManifestPath};
+use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath};
 
 pub(crate) fn get(
     cargo_toml: Option<&ManifestPath>,
     target: Option<&str>,
-    config: &CargoConfig,
+    extra_env: &FxHashMap,
 ) -> Vec {
     let _p = profile::span("rustc_cfg::get");
     let mut res = Vec::with_capacity(6 * 2 + 1);
@@ -22,7 +23,7 @@ pub(crate) fn get(
         }
     }
 
-    match get_rust_cfgs(cargo_toml, target, config) {
+    match get_rust_cfgs(cargo_toml, target, extra_env) {
         Ok(rustc_cfgs) => {
             tracing::debug!(
                 "rustc cfgs found: {:?}",
@@ -42,11 +43,11 @@ pub(crate) fn get(
 fn get_rust_cfgs(
     cargo_toml: Option<&ManifestPath>,
     target: Option<&str>,
-    config: &CargoConfig,
+    extra_env: &FxHashMap,
 ) -> Result {
     if let Some(cargo_toml) = cargo_toml {
         let mut cargo_config = Command::new(toolchain::cargo());
-        cargo_config.envs(&config.extra_env);
+        cargo_config.envs(extra_env);
         cargo_config
             .current_dir(cargo_toml.parent())
             .args(&["-Z", "unstable-options", "rustc", "--print", "cfg"])
@@ -61,7 +62,7 @@ fn get_rust_cfgs(
     }
     // using unstable cargo features failed, fall back to using plain rustc
     let mut cmd = Command::new(toolchain::rustc());
-    cmd.envs(&config.extra_env);
+    cmd.envs(extra_env);
     cmd.args(&["--print", "cfg", "-O"]);
     if let Some(target) = target {
         cmd.args(&["--target", target]);
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index 3282719fef..bc37e3d132 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -9,8 +9,9 @@ use std::{env, fs, iter, ops, path::PathBuf, process::Command};
 use anyhow::{format_err, Result};
 use la_arena::{Arena, Idx};
 use paths::{AbsPath, AbsPathBuf};
+use rustc_hash::FxHashMap;
 
-use crate::{utf8_stdout, CargoConfig, ManifestPath};
+use crate::{utf8_stdout, ManifestPath};
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Sysroot {
@@ -66,23 +67,37 @@ impl Sysroot {
     pub fn crates<'a>(&'a self) -> impl Iterator + ExactSizeIterator + 'a {
         self.crates.iter().map(|(id, _data)| id)
     }
+}
 
-    pub fn discover(dir: &AbsPath, config: &CargoConfig) -> Result {
+impl Sysroot {
+    pub fn discover(dir: &AbsPath, extra_env: &FxHashMap) -> Result {
         tracing::debug!("Discovering sysroot for {}", dir.display());
-        let sysroot_dir = discover_sysroot_dir(dir, config)?;
-        let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, dir, config)?;
+        let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
+        let sysroot_src_dir =
+            discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?;
         let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?;
         Ok(res)
     }
 
-    pub fn discover_rustc(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option {
+    pub fn discover_rustc(
+        cargo_toml: &ManifestPath,
+        extra_env: &FxHashMap,
+    ) -> Option {
         tracing::debug!("Discovering rustc source for {}", cargo_toml.display());
         let current_dir = cargo_toml.parent();
-        discover_sysroot_dir(current_dir, config)
+        discover_sysroot_dir(current_dir, extra_env)
             .ok()
             .and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
     }
 
+    pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result {
+        let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
+            format_err!("can't load standard library from sysroot {}", sysroot_dir.display())
+        })?;
+        let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?;
+        Ok(res)
+    }
+
     pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf) -> Result {
         let mut sysroot =
             Sysroot { root: sysroot_dir, src_root: sysroot_src_dir, crates: Arena::default() };
@@ -146,35 +161,43 @@ impl Sysroot {
     }
 }
 
-fn discover_sysroot_dir(current_dir: &AbsPath, config: &CargoConfig) -> Result {
+fn discover_sysroot_dir(
+    current_dir: &AbsPath,
+    extra_env: &FxHashMap,
+) -> Result {
     let mut rustc = Command::new(toolchain::rustc());
-    rustc.envs(&config.extra_env);
+    rustc.envs(extra_env);
     rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
     tracing::debug!("Discovering sysroot by {:?}", rustc);
     let stdout = utf8_stdout(rustc)?;
     Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
 }
 
-fn discover_sysroot_src_dir(
-    sysroot_path: &AbsPathBuf,
-    current_dir: &AbsPath,
-    config: &CargoConfig,
-) -> Result {
+fn discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option {
     if let Ok(path) = env::var("RUST_SRC_PATH") {
-        let path = AbsPathBuf::try_from(path.as_str())
-            .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
-        let core = path.join("core");
-        if fs::metadata(&core).is_ok() {
-            tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
-            return Ok(path);
+        if let Ok(path) = AbsPathBuf::try_from(path.as_str()) {
+            let core = path.join("core");
+            if fs::metadata(&core).is_ok() {
+                tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
+                return Some(path);
+            }
+            tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
+        } else {
+            tracing::debug!("RUST_SRC_PATH is set, but is invalid, ignoring");
         }
-        tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
     }
 
     get_rust_src(sysroot_path)
+}
+fn discover_sysroot_src_dir_or_add_component(
+    sysroot_path: &AbsPathBuf,
+    current_dir: &AbsPath,
+    extra_env: &FxHashMap,
+) -> Result {
+    discover_sysroot_src_dir(sysroot_path)
         .or_else(|| {
             let mut rustup = Command::new(toolchain::rustup());
-            rustup.envs(&config.extra_env);
+            rustup.envs(extra_env);
             rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
             utf8_stdout(rustup).ok()?;
             get_rust_src(sysroot_path)
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index bea624bd54..e2444e2497 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -10,8 +10,8 @@ use paths::{AbsPath, AbsPathBuf};
 use serde::de::DeserializeOwned;
 
 use crate::{
-    CargoConfig, CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace,
-    Sysroot, WorkspaceBuildScripts,
+    CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot,
+    WorkspaceBuildScripts,
 };
 
 fn load_cargo(file: &str) -> CrateGraph {
@@ -101,7 +101,7 @@ fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
                 Some(FileId(counter))
             }
         },
-        &CargoConfig::default(),
+        &Default::default(),
     )
 }
 
@@ -185,6 +185,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -260,6 +263,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -335,6 +341,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -410,6 +419,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -477,6 +489,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                             repo: Some(
                                 "https://github.com/rust-lang/libc",
                             ),
+                            name: Some(
+                                "libc",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -567,6 +582,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -644,6 +662,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -721,6 +742,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -798,6 +822,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -865,6 +892,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                             repo: Some(
                                 "https://github.com/rust-lang/libc",
                             ),
+                            name: Some(
+                                "libc",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -946,6 +976,9 @@ fn cargo_hello_world_project_model() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -1023,6 +1056,9 @@ fn cargo_hello_world_project_model() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -1100,6 +1136,9 @@ fn cargo_hello_world_project_model() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -1177,6 +1216,9 @@ fn cargo_hello_world_project_model() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello-world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -1244,6 +1286,9 @@ fn cargo_hello_world_project_model() {
                             repo: Some(
                                 "https://github.com/rust-lang/libc",
                             ),
+                            name: Some(
+                                "libc",
+                            ),
                         },
                         is_proc_macro: false,
                     },
@@ -1804,6 +1849,9 @@ fn rust_project_hello_world_project_model() {
                         ),
                         origin: CratesIo {
                             repo: None,
+                            name: Some(
+                                "hello_world",
+                            ),
                         },
                         is_proc_macro: false,
                     },
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index bc4ab45dae..72ddf80928 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -21,8 +21,8 @@ use crate::{
     cfg_flag::CfgFlag,
     rustc_cfg,
     sysroot::SysrootCrate,
-    utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, ProjectJson, ProjectManifest, Sysroot,
-    TargetKind, WorkspaceBuildScripts,
+    utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, Package, ProjectJson, ProjectManifest,
+    Sysroot, TargetKind, WorkspaceBuildScripts,
 };
 
 /// A set of cfg-overrides per crate.
@@ -156,7 +156,11 @@ impl ProjectWorkspace {
                 })?;
                 let project_location = project_json.parent().to_path_buf();
                 let project_json = ProjectJson::new(&project_location, data);
-                ProjectWorkspace::load_inline(project_json, config.target.as_deref(), config)?
+                ProjectWorkspace::load_inline(
+                    project_json,
+                    config.target.as_deref(),
+                    &config.extra_env,
+                )?
             }
             ProjectManifest::CargoToml(cargo_toml) => {
                 let cargo_version = utf8_stdout({
@@ -184,20 +188,33 @@ impl ProjectWorkspace {
                 })?;
                 let cargo = CargoWorkspace::new(meta);
 
-                let sysroot = if config.no_sysroot {
-                    None
-                } else {
-                    Some(Sysroot::discover(cargo_toml.parent(), config).with_context(|| {
-                        format!(
+                let sysroot = match &config.sysroot {
+                    Some(RustcSource::Path(path)) => {
+                        Some(Sysroot::with_sysroot_dir(path.clone()).with_context(|| {
+                            format!(
+                                "Failed to find sysroot for Cargo.toml file {}.",
+                                cargo_toml.display()
+                            )
+                        })?)
+                    }
+                    Some(RustcSource::Discover) => Some(
+                        Sysroot::discover(cargo_toml.parent(), &config.extra_env).with_context(
+                            || {
+                                format!(
                             "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
                             cargo_toml.display()
                         )
-                    })?)
+                            },
+                        )?,
+                    ),
+                    None => None,
                 };
 
                 let rustc_dir = match &config.rustc_source {
                     Some(RustcSource::Path(path)) => ManifestPath::try_from(path.clone()).ok(),
-                    Some(RustcSource::Discover) => Sysroot::discover_rustc(&cargo_toml, config),
+                    Some(RustcSource::Discover) => {
+                        Sysroot::discover_rustc(&cargo_toml, &config.extra_env)
+                    }
                     None => None,
                 };
 
@@ -217,7 +234,8 @@ impl ProjectWorkspace {
                     None => None,
                 };
 
-                let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), config);
+                let rustc_cfg =
+                    rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env);
 
                 let cfg_overrides = config.cfg_overrides();
                 ProjectWorkspace::Cargo {
@@ -238,7 +256,7 @@ impl ProjectWorkspace {
     pub fn load_inline(
         project_json: ProjectJson,
         target: Option<&str>,
-        config: &CargoConfig,
+        extra_env: &FxHashMap,
     ) -> Result {
         let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
             (Some(sysroot), Some(sysroot_src)) => Some(Sysroot::load(sysroot, sysroot_src)?),
@@ -260,7 +278,7 @@ impl ProjectWorkspace {
             (None, None) => None,
         };
 
-        let rustc_cfg = rustc_cfg::get(None, target, config);
+        let rustc_cfg = rustc_cfg::get(None, target, extra_env);
         Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
     }
 
@@ -270,9 +288,9 @@ impl ProjectWorkspace {
                 .first()
                 .and_then(|it| it.parent())
                 .ok_or_else(|| format_err!("No detached files to load"))?,
-            &CargoConfig::default(),
+            &Default::default(),
         )?;
-        let rustc_cfg = rustc_cfg::get(None, None, &CargoConfig::default());
+        let rustc_cfg = rustc_cfg::get(None, None, &Default::default());
         Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
     }
 
@@ -306,6 +324,13 @@ impl ProjectWorkspace {
     /// The return type contains the path and whether or not
     /// the root is a member of the current workspace
     pub fn to_roots(&self) -> Vec {
+        let mk_sysroot = |sysroot: Option<&Sysroot>| {
+            sysroot.map(|sysroot| PackageRoot {
+                is_local: false,
+                include: vec![sysroot.src_root().to_path_buf()],
+                exclude: Vec::new(),
+            })
+        };
         match self {
             ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
                 .crates()
@@ -316,13 +341,7 @@ impl ProjectWorkspace {
                 })
                 .collect::>()
                 .into_iter()
-                .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
-                    sysroot.crates().map(move |krate| PackageRoot {
-                        is_local: false,
-                        include: vec![sysroot[krate].root.parent().to_path_buf()],
-                        exclude: Vec::new(),
-                    })
-                }))
+                .chain(mk_sysroot(sysroot.as_ref()))
                 .collect::>(),
             ProjectWorkspace::Cargo {
                 cargo,
@@ -371,11 +390,7 @@ impl ProjectWorkspace {
                         }
                         PackageRoot { is_local, include, exclude }
                     })
-                    .chain(sysroot.iter().map(|sysroot| PackageRoot {
-                        is_local: false,
-                        include: vec![sysroot.src_root().to_path_buf()],
-                        exclude: Vec::new(),
-                    }))
+                    .chain(mk_sysroot(sysroot.as_ref()))
                     .chain(rustc.iter().flat_map(|rustc| {
                         rustc.packages().map(move |krate| PackageRoot {
                             is_local: false,
@@ -392,11 +407,7 @@ impl ProjectWorkspace {
                     include: vec![detached_file.clone()],
                     exclude: Vec::new(),
                 })
-                .chain(sysroot.crates().map(|krate| PackageRoot {
-                    is_local: false,
-                    include: vec![sysroot[krate].root.parent().to_path_buf()],
-                    exclude: Vec::new(),
-                }))
+                .chain(mk_sysroot(Some(sysroot)))
                 .collect(),
         }
     }
@@ -419,7 +430,7 @@ impl ProjectWorkspace {
         &self,
         load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
         load: &mut dyn FnMut(&AbsPath) -> Option,
-        config: &CargoConfig,
+        extra_env: &FxHashMap,
     ) -> CrateGraph {
         let _p = profile::span("ProjectWorkspace::to_crate_graph");
 
@@ -430,7 +441,7 @@ impl ProjectWorkspace {
                 load,
                 project,
                 sysroot,
-                config,
+                extra_env,
             ),
             ProjectWorkspace::Cargo {
                 cargo,
@@ -469,7 +480,7 @@ fn project_json_to_crate_graph(
     load: &mut dyn FnMut(&AbsPath) -> Option,
     project: &ProjectJson,
     sysroot: &Option,
-    config: &CargoConfig,
+    extra_env: &FxHashMap,
 ) -> CrateGraph {
     let mut crate_graph = CrateGraph::default();
     let sysroot_deps = sysroot
@@ -497,7 +508,7 @@ fn project_json_to_crate_graph(
             let target_cfgs = match krate.target.as_deref() {
                 Some(target) => cfg_cache
                     .entry(target)
-                    .or_insert_with(|| rustc_cfg::get(None, Some(target), config)),
+                    .or_insert_with(|| rustc_cfg::get(None, Some(target), extra_env)),
                 None => &rustc_cfg,
             };
 
@@ -516,9 +527,15 @@ fn project_json_to_crate_graph(
                     proc_macro,
                     krate.is_proc_macro,
                     if krate.display_name.is_some() {
-                        CrateOrigin::CratesIo { repo: krate.repository.clone() }
+                        CrateOrigin::CratesIo {
+                            repo: krate.repository.clone(),
+                            name: krate
+                                .display_name
+                                .clone()
+                                .map(|n| n.canonical_name().to_string()),
+                        }
                     } else {
-                        CrateOrigin::CratesIo { repo: None }
+                        CrateOrigin::CratesIo { repo: None, name: None }
                     },
                 ),
             )
@@ -630,6 +647,8 @@ fn cargo_to_crate_graph(
                     lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
                     pkg_to_lib_crate.insert(pkg, crate_id);
                 }
+                // Even crates that don't set proc-macro = true are allowed to depend on proc_macro
+                // (just none of the APIs work when called outside of a proc macro).
                 if let Some(proc_macro) = libproc_macro {
                     add_dep_with_prelude(
                         &mut crate_graph,
@@ -645,19 +664,19 @@ fn cargo_to_crate_graph(
         }
 
         // Set deps to the core, std and to the lib target of the current package
-        for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
+        for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
             // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
-            public_deps.add(*from, &mut crate_graph);
+            public_deps.add(from, &mut crate_graph);
 
             if let Some((to, name)) = lib_tgt.clone() {
-                if to != *from && *kind != TargetKind::BuildScript {
+                if to != from && kind != TargetKind::BuildScript {
                     // (build script can not depend on its library target)
 
                     // For root projects with dashes in their name,
                     // cargo metadata does not do any normalization,
                     // so we do it ourselves currently
                     let name = CrateName::normalize_dashes(&name);
-                    add_dep(&mut crate_graph, *from, name, to);
+                    add_dep(&mut crate_graph, from, name, to);
                 }
             }
         }
@@ -669,17 +688,17 @@ fn cargo_to_crate_graph(
         for dep in cargo[pkg].dependencies.iter() {
             let name = CrateName::new(&dep.name).unwrap();
             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
-                for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
-                    if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
+                for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
+                    if dep.kind == DepKind::Build && kind != TargetKind::BuildScript {
                         // Only build scripts may depend on build dependencies.
                         continue;
                     }
-                    if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
+                    if dep.kind != DepKind::Build && kind == TargetKind::BuildScript {
                         // Build scripts may only depend on build dependencies.
                         continue;
                     }
 
-                    add_dep(&mut crate_graph, *from, name.clone(), to)
+                    add_dep(&mut crate_graph, from, name.clone(), to)
                 }
             }
         }
@@ -690,9 +709,9 @@ fn cargo_to_crate_graph(
         // and create dependencies on them for the crates which opt-in to that
         if let Some(rustc_workspace) = rustc {
             handle_rustc_crates(
+                &mut crate_graph,
                 rustc_workspace,
                 load,
-                &mut crate_graph,
                 &cfg_options,
                 override_cfg,
                 load_proc_macro,
@@ -736,14 +755,17 @@ fn detached_files_to_crate_graph(
         let detached_file_crate = crate_graph.add_crate_root(
             file_id,
             Edition::CURRENT,
-            display_name,
+            display_name.clone(),
             None,
             cfg_options.clone(),
             cfg_options.clone(),
             Env::default(),
             Ok(Vec::new()),
             false,
-            CrateOrigin::CratesIo { repo: None },
+            CrateOrigin::CratesIo {
+                repo: None,
+                name: display_name.map(|n| n.canonical_name().to_string()),
+            },
         );
 
         public_deps.add(detached_file_crate, &mut crate_graph);
@@ -752,16 +774,16 @@ fn detached_files_to_crate_graph(
 }
 
 fn handle_rustc_crates(
+    crate_graph: &mut CrateGraph,
     rustc_workspace: &CargoWorkspace,
     load: &mut dyn FnMut(&AbsPath) -> Option,
-    crate_graph: &mut CrateGraph,
     cfg_options: &CfgOptions,
     override_cfg: &CfgOverrides,
     load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
-    pkg_to_lib_crate: &mut FxHashMap, CrateId>,
+    pkg_to_lib_crate: &mut FxHashMap,
     public_deps: &SysrootPublicDeps,
     cargo: &CargoWorkspace,
-    pkg_crates: &FxHashMap, Vec<(CrateId, TargetKind)>>,
+    pkg_crates: &FxHashMap>,
     build_scripts: &WorkspaceBuildScripts,
 ) {
     let mut rustc_pkg_crates = FxHashMap::default();
@@ -775,8 +797,8 @@ fn handle_rustc_crates(
         let mut queue = VecDeque::new();
         queue.push_back(root_pkg);
         while let Some(pkg) = queue.pop_front() {
-            // Don't duplicate packages if they are dependended on a diamond pattern
-            // N.B. if this line is omitted, we try to analyse over 4_800_000 crates
+            // Don't duplicate packages if they are dependent on a diamond pattern
+            // N.B. if this line is omitted, we try to analyze over 4_800_000 crates
             // which is not ideal
             if rustc_pkg_crates.contains_key(&pkg) {
                 continue;
@@ -919,7 +941,7 @@ fn add_target_crate_root(
         env,
         proc_macro,
         is_proc_macro,
-        CrateOrigin::CratesIo { repo: pkg.repository.clone() },
+        CrateOrigin::CratesIo { repo: pkg.repository.clone(), name: Some(pkg.name.clone()) },
     )
 }
 
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 5392589186..5445028536 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -18,14 +18,14 @@ name = "rust-analyzer"
 path = "src/bin/main.rs"
 
 [dependencies]
-anyhow = "1.0.57"
+anyhow = "1.0.62"
 crossbeam-channel = "0.5.5"
 dissimilar = "1.0.4"
-itertools = "0.10.3"
+itertools = "0.10.5"
 scip = "0.1.1"
 lsp-types = { version = "0.93.1", features = ["proposed"] }
 parking_lot = "0.12.1"
-xflags = "0.2.4"
+xflags = "0.3.0"
 oorandom = "11.1.3"
 rustc-hash = "1.1.0"
 serde = { version = "1.0.137", features = ["derive"] }
@@ -33,10 +33,10 @@ serde_json = { version = "1.0.81", features = ["preserve_order"] }
 threadpool = "1.8.1"
 rayon = "1.5.3"
 num_cpus = "1.13.1"
-mimalloc = { version = "0.1.29", default-features = false, optional = true }
-lsp-server = { version = "0.6.0", path = "../../lib/lsp-server" }
+mimalloc = { version = "0.1.30", default-features = false, optional = true }
+lsp-server = { version = "0.7.0", path = "../../lib/lsp-server" }
 tracing = "0.1.35"
-tracing-subscriber = { version = "0.3.14", default-features = false, features = [
+tracing-subscriber = { version = "0.3.16", default-features = false, features = [
     "env-filter",
     "registry",
     "fmt",
@@ -87,7 +87,6 @@ jemalloc = ["jemallocator", "profile/jemalloc"]
 force-always-assert = ["always-assert/force"]
 in-rust-tree = [
     "proc-macro-srv/sysroot-abi",
-    "sourcegen/in-rust-tree",
     "ide/in-rust-tree",
     "syntax/in-rust-tree",
 ]
diff --git a/crates/rust-analyzer/src/bin/logger.rs b/crates/rust-analyzer/src/bin/logger.rs
index 298814af5a..ac10721d95 100644
--- a/crates/rust-analyzer/src/bin/logger.rs
+++ b/crates/rust-analyzer/src/bin/logger.rs
@@ -132,7 +132,7 @@ where
 
             let ext = span.extensions();
 
-            // `FormattedFields` is a a formatted representation of the span's
+            // `FormattedFields` is a formatted representation of the span's
             // fields, which is stored in its extensions by the `fmt` layer's
             // `new_span` method. The fields will have been formatted
             // by the same field formatter that's provided to the event
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index f6a6802972..eabfcf1944 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -37,16 +37,15 @@ fn main() {
         process::exit(code);
     }
 
-    if let Err(err) = try_main() {
+    let flags = flags::RustAnalyzer::from_env_or_exit();
+    if let Err(err) = try_main(flags) {
         tracing::error!("Unexpected error: {}", err);
         eprintln!("{}", err);
         process::exit(101);
     }
 }
 
-fn try_main() -> Result<()> {
-    let flags = flags::RustAnalyzer::from_env()?;
-
+fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
     #[cfg(debug_assertions)]
     if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
         #[allow(unused_mut)]
@@ -76,10 +75,6 @@ fn try_main() -> Result<()> {
                 println!("rust-analyzer {}", rust_analyzer::version());
                 return Ok(());
             }
-            if cmd.help {
-                println!("{}", flags::RustAnalyzer::HELP);
-                return Ok(());
-            }
             with_extra_thread("LspServer", run_server)?;
         }
         flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 1c39e9391a..e1675a030c 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -4,7 +4,7 @@ use std::mem;
 
 use cfg::{CfgAtom, CfgExpr};
 use ide::{FileId, RunnableKind, TestId};
-use project_model::{self, ManifestPath, TargetKind};
+use project_model::{self, CargoFeatures, ManifestPath, TargetKind};
 use vfs::AbsPathBuf;
 
 use crate::{global_state::GlobalStateSnapshot, Result};
@@ -35,41 +35,41 @@ impl CargoTargetSpec {
 
         match kind {
             RunnableKind::Test { test_id, attr } => {
-                args.push("test".to_string());
+                args.push("test".to_owned());
                 extra_args.push(test_id.to_string());
                 if let TestId::Path(_) = test_id {
-                    extra_args.push("--exact".to_string());
+                    extra_args.push("--exact".to_owned());
                 }
-                extra_args.push("--nocapture".to_string());
+                extra_args.push("--nocapture".to_owned());
                 if attr.ignore {
-                    extra_args.push("--ignored".to_string());
+                    extra_args.push("--ignored".to_owned());
                 }
             }
             RunnableKind::TestMod { path } => {
-                args.push("test".to_string());
-                extra_args.push(path.to_string());
-                extra_args.push("--nocapture".to_string());
+                args.push("test".to_owned());
+                extra_args.push(path.clone());
+                extra_args.push("--nocapture".to_owned());
             }
             RunnableKind::Bench { test_id } => {
-                args.push("bench".to_string());
+                args.push("bench".to_owned());
                 extra_args.push(test_id.to_string());
                 if let TestId::Path(_) = test_id {
-                    extra_args.push("--exact".to_string());
+                    extra_args.push("--exact".to_owned());
                 }
-                extra_args.push("--nocapture".to_string());
+                extra_args.push("--nocapture".to_owned());
             }
             RunnableKind::DocTest { test_id } => {
-                args.push("test".to_string());
-                args.push("--doc".to_string());
+                args.push("test".to_owned());
+                args.push("--doc".to_owned());
                 extra_args.push(test_id.to_string());
-                extra_args.push("--nocapture".to_string());
+                extra_args.push("--nocapture".to_owned());
             }
             RunnableKind::Bin => {
                 let subcommand = match spec {
                     Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => "test",
                     _ => "run",
                 };
-                args.push(subcommand.to_string());
+                args.push(subcommand.to_owned());
             }
         }
 
@@ -82,29 +82,35 @@ impl CargoTargetSpec {
         };
 
         let cargo_config = snap.config.cargo();
-        if cargo_config.all_features {
-            args.push("--all-features".to_string());
 
-            for feature in target_required_features {
-                args.push("--features".to_string());
-                args.push(feature);
-            }
-        } else {
-            let mut features = Vec::new();
-            if let Some(cfg) = cfg.as_ref() {
-                required_features(cfg, &mut features);
+        match &cargo_config.features {
+            CargoFeatures::All => {
+                args.push("--all-features".to_owned());
+                for feature in target_required_features {
+                    args.push("--features".to_owned());
+                    args.push(feature);
+                }
             }
+            CargoFeatures::Selected { features, no_default_features } => {
+                let mut feats = Vec::new();
+                if let Some(cfg) = cfg.as_ref() {
+                    required_features(cfg, &mut feats);
+                }
 
-            features.extend(cargo_config.features);
-            features.extend(target_required_features);
+                feats.extend(features.iter().cloned());
+                feats.extend(target_required_features);
 
-            features.dedup();
-            for feature in features {
-                args.push("--features".to_string());
-                args.push(feature);
+                feats.dedup();
+                for feature in feats {
+                    args.push("--features".to_owned());
+                    args.push(feature);
+                }
+
+                if *no_default_features {
+                    args.push("--no-default-features".to_owned());
+                }
             }
         }
-
         Ok((args, extra_args))
     }
 
@@ -136,7 +142,7 @@ impl CargoTargetSpec {
     }
 
     pub(crate) fn push_to(self, buf: &mut Vec, kind: &RunnableKind) {
-        buf.push("--package".to_string());
+        buf.push("--package".to_owned());
         buf.push(self.package);
 
         // Can't mix --doc with other target flags
@@ -145,23 +151,23 @@ impl CargoTargetSpec {
         }
         match self.target_kind {
             TargetKind::Bin => {
-                buf.push("--bin".to_string());
+                buf.push("--bin".to_owned());
                 buf.push(self.target);
             }
             TargetKind::Test => {
-                buf.push("--test".to_string());
+                buf.push("--test".to_owned());
                 buf.push(self.target);
             }
             TargetKind::Bench => {
-                buf.push("--bench".to_string());
+                buf.push("--bench".to_owned());
                 buf.push(self.target);
             }
             TargetKind::Example => {
-                buf.push("--example".to_string());
+                buf.push("--example".to_owned());
                 buf.push(self.target);
             }
             TargetKind::Lib => {
-                buf.push("--lib".to_string());
+                buf.push("--lib".to_owned());
             }
             TargetKind::Other | TargetKind::BuildScript => (),
         }
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 80128e43fd..01fccc83e8 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -24,7 +24,7 @@ use ide_db::base_db::{
 use itertools::Itertools;
 use oorandom::Rand32;
 use profile::{Bytes, StopWatch};
-use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
+use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustcSource};
 use rayon::prelude::*;
 use rustc_hash::FxHashSet;
 use stdx::format_to;
@@ -55,7 +55,10 @@ impl flags::AnalysisStats {
         };
 
         let mut cargo_config = CargoConfig::default();
-        cargo_config.no_sysroot = self.no_sysroot;
+        cargo_config.sysroot = match self.no_sysroot {
+            true => None,
+            false => Some(RustcSource::Discover),
+        };
         let load_cargo_config = LoadCargoConfig {
             load_out_dirs_from_check: !self.disable_build_scripts,
             with_proc_macro: !self.disable_proc_macros,
@@ -81,7 +84,7 @@ impl flags::AnalysisStats {
         };
 
         let (host, vfs, _proc_macro) =
-            load_workspace(workspace, &cargo_config, &load_cargo_config)?;
+            load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
         let db = host.raw_database();
         eprint!("{:<20} {}", "Database loaded:", db_load_sw.elapsed());
         eprint!(" (metadata {}", metadata_time);
diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs
index aa32654fbd..5bcc97e226 100644
--- a/crates/rust-analyzer/src/cli/flags.rs
+++ b/crates/rust-analyzer/src/cli/flags.rs
@@ -31,8 +31,6 @@ xflags::xflags! {
         default cmd lsp-server {
             /// Print version.
             optional --version
-            /// Print help.
-            optional -h, --help
 
             /// Dump a LSP config JSON schema.
             optional --print-config-schema
@@ -54,10 +52,10 @@ xflags::xflags! {
         }
 
         /// Batch typecheck project and print summary statistics
-        cmd analysis-stats
+        cmd analysis-stats {
             /// Directory with Cargo.toml.
             required path: PathBuf
-        {
+
             optional --output format: OutputFormat
 
             /// Randomize order in which crates, modules, and items are processed.
@@ -84,38 +82,37 @@ xflags::xflags! {
             optional --skip-inference
         }
 
-        cmd diagnostics
+        cmd diagnostics {
             /// Directory with Cargo.toml.
             required path: PathBuf
-        {
+
             /// Don't run build scripts or load `OUT_DIR` values by running `cargo check` before analysis.
             optional --disable-build-scripts
             /// Don't use expand proc macros.
             optional --disable-proc-macros
         }
 
-        cmd ssr
+        cmd ssr {
             /// A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
             repeated rule: SsrRule
-        {}
+        }
 
-        cmd search
+        cmd search {
             /// A structured search replace pattern (`$a.foo($b)`)
             repeated pattern: SsrPattern
-        {
             /// Prints debug information for any nodes with source exactly equal to snippet.
             optional --debug snippet: String
         }
 
         cmd proc-macro {}
 
-        cmd lsif
+        cmd lsif {
             required path: PathBuf
-        {}
+        }
 
-        cmd scip
+        cmd scip {
             required path: PathBuf
-        {}
+        }
     }
 }
 
@@ -150,7 +147,6 @@ pub enum RustAnalyzerCmd {
 #[derive(Debug)]
 pub struct LspServer {
     pub version: bool,
-    pub help: bool,
     pub print_config_schema: bool,
 }
 
@@ -218,7 +214,10 @@ pub struct Scip {
 }
 
 impl RustAnalyzer {
-    pub const HELP: &'static str = Self::HELP_;
+    #[allow(dead_code)]
+    pub fn from_env_or_exit() -> Self {
+        Self::from_env_or_exit_()
+    }
 
     #[allow(dead_code)]
     pub fn from_env() -> xflags::Result {
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 88953096e2..5dba545b87 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -6,7 +6,7 @@ use anyhow::Result;
 use crossbeam_channel::{unbounded, Receiver};
 use hir::db::DefDatabase;
 use ide::{AnalysisHost, Change};
-use ide_db::base_db::CrateGraph;
+use ide_db::{base_db::CrateGraph, FxHashMap};
 use proc_macro_api::ProcMacroServer;
 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
 use vfs::{loader::Handle, AbsPath, AbsPathBuf};
@@ -38,7 +38,7 @@ pub fn load_workspace_at(
         workspace.set_build_scripts(build_scripts)
     }
 
-    load_workspace(workspace, cargo_config, load_config)
+    load_workspace(workspace, &cargo_config.extra_env, load_config)
 }
 
 // Note: Since this function is used by external tools that use rust-analyzer as a library
@@ -48,7 +48,7 @@ pub fn load_workspace_at(
 // these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
 pub fn load_workspace(
     ws: ProjectWorkspace,
-    cargo_config: &CargoConfig,
+    extra_env: &FxHashMap,
     load_config: &LoadCargoConfig,
 ) -> Result<(AnalysisHost, vfs::Vfs, Option)> {
     let (sender, receiver) = unbounded();
@@ -60,10 +60,26 @@ pub fn load_workspace(
     };
 
     let proc_macro_client = if load_config.with_proc_macro {
-        let path = AbsPathBuf::assert(std::env::current_exe()?);
-        Ok(ProcMacroServer::spawn(path, &["proc-macro"]).unwrap())
+        let mut path = AbsPathBuf::assert(std::env::current_exe()?);
+        let mut args = vec!["proc-macro"];
+
+        if let ProjectWorkspace::Cargo { sysroot, .. } | ProjectWorkspace::Json { sysroot, .. } =
+            &ws
+        {
+            if let Some(sysroot) = sysroot.as_ref() {
+                let standalone_server_name =
+                    format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
+                let server_path = sysroot.root().join("libexec").join(&standalone_server_name);
+                if std::fs::metadata(&server_path).is_ok() {
+                    path = server_path;
+                    args = vec![];
+                }
+            }
+        }
+
+        ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|e| e.to_string())
     } else {
-        Err("proc macro server not started".to_owned())
+        Err("proc macro server disabled".to_owned())
     };
 
     let crate_graph = ws.to_crate_graph(
@@ -76,7 +92,7 @@ pub fn load_workspace(
             vfs.set_file_contents(path.clone(), contents);
             vfs.file_id(&path)
         },
-        cargo_config,
+        extra_env,
     );
 
     let project_folders = ProjectFolders::new(&[ws], &[]);
diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs
index 79577bf78c..748306ea57 100644
--- a/crates/rust-analyzer/src/cli/lsif.rs
+++ b/crates/rust-analyzer/src/cli/lsif.rs
@@ -300,7 +300,7 @@ impl flags::Lsif {
         let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
 
         let (host, vfs, _proc_macro) =
-            load_workspace(workspace, &cargo_config, &load_cargo_config)?;
+            load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
         let db = host.raw_database();
         let analysis = host.analysis();
 
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 05c16bb39e..2c29b3ee3a 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -40,7 +40,8 @@ impl flags::Scip {
 
         let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
 
-        let (host, vfs, _) = load_workspace(workspace, &cargo_config, &load_cargo_config)?;
+        let (host, vfs, _) =
+            load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
         let db = host.raw_database();
         let analysis = host.analysis();
 
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 9ef79e6f38..577a8640a4 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,7 +7,7 @@
 //! configure the server itself, feature flags are passed into analysis, and
 //! tweak things like automatic insertion of `()` in completions.
 
-use std::{ffi::OsString, fmt, iter, path::PathBuf};
+use std::{fmt, iter, path::PathBuf};
 
 use flycheck::FlycheckConfig;
 use ide::{
@@ -22,7 +22,8 @@ use ide_db::{
 use itertools::Itertools;
 use lsp_types::{ClientCapabilities, MarkupKind};
 use project_model::{
-    CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
+    CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource,
+    UnsetTestCrates,
 };
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::{de::DeserializeOwned, Deserialize};
@@ -90,11 +91,16 @@ config_data! {
         /// List of features to activate.
         ///
         /// Set this to `"all"` to pass `--all-features` to cargo.
-        cargo_features: CargoFeatures      = "[]",
+        cargo_features: CargoFeaturesDef      = "[]",
         /// Whether to pass `--no-default-features` to cargo.
         cargo_noDefaultFeatures: bool    = "false",
-        /// Internal config for debugging, disables loading of sysroot crates.
-        cargo_noSysroot: bool            = "false",
+        /// Relative path to the sysroot, or "discover" to try to automatically find it via
+        /// "rustc --print sysroot".
+        ///
+        /// Unsetting this disables sysroot loading.
+        ///
+        /// This option does not take effect until rust-analyzer is restarted.
+        cargo_sysroot: Option    = "\"discover\"",
         /// Compilation target override (target triple).
         cargo_target: Option     = "null",
         /// Unsets `#[cfg(test)]` for the specified crates.
@@ -109,12 +115,13 @@ config_data! {
         /// Extra arguments for `cargo check`.
         checkOnSave_extraArgs: Vec               = "[]",
         /// Extra environment variables that will be set when running `cargo check`.
+        /// Extends `#rust-analyzer.cargo.extraEnv#`.
         checkOnSave_extraEnv: FxHashMap = "{}",
         /// List of features to activate. Defaults to
         /// `#rust-analyzer.cargo.features#`.
         ///
         /// Set to `"all"` to pass `--all-features` to Cargo.
-        checkOnSave_features: Option      = "null",
+        checkOnSave_features: Option      = "null",
         /// Whether to pass `--no-default-features` to Cargo. Defaults to
         /// `#rust-analyzer.cargo.noDefaultFeatures#`.
         checkOnSave_noDefaultFeatures: Option      = "null",
@@ -975,15 +982,17 @@ impl Config {
         self.data.lru_capacity
     }
 
-    pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec)> {
+    pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, /* is path explicitly set */ bool)> {
         if !self.data.procMacro_enable {
             return None;
         }
-        let path = match &self.data.procMacro_server {
-            Some(it) => self.root_path.join(it),
-            None => AbsPathBuf::assert(std::env::current_exe().ok()?),
-        };
-        Some((path, vec!["proc-macro".into()]))
+        Some(match &self.data.procMacro_server {
+            Some(it) => (
+                AbsPathBuf::try_from(it.clone()).unwrap_or_else(|path| self.root_path.join(path)),
+                true,
+            ),
+            None => (AbsPathBuf::assert(std::env::current_exe().ok()?), false),
+        })
     }
 
     pub fn dummy_replacements(&self) -> &FxHashMap, Box<[Box]>> {
@@ -1026,16 +1035,24 @@ impl Config {
                 RustcSource::Path(self.root_path.join(rustc_src))
             }
         });
+        let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| {
+            if sysroot == "discover" {
+                RustcSource::Discover
+            } else {
+                RustcSource::Path(self.root_path.join(sysroot))
+            }
+        });
 
         CargoConfig {
-            no_default_features: self.data.cargo_noDefaultFeatures,
-            all_features: matches!(self.data.cargo_features, CargoFeatures::All),
             features: match &self.data.cargo_features {
-                CargoFeatures::All => vec![],
-                CargoFeatures::Listed(it) => it.clone(),
+                CargoFeaturesDef::All => CargoFeatures::All,
+                CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
+                    features: features.clone(),
+                    no_default_features: self.data.cargo_noDefaultFeatures,
+                },
             },
             target: self.data.cargo_target.clone(),
-            no_sysroot: self.data.cargo_noSysroot,
+            sysroot,
             rustc_source,
             unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
             wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
@@ -1086,7 +1103,7 @@ impl Config {
                     .unwrap_or(self.data.cargo_noDefaultFeatures),
                 all_features: matches!(
                     self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
-                    CargoFeatures::All
+                    CargoFeaturesDef::All
                 ),
                 features: match self
                     .data
@@ -1094,8 +1111,8 @@ impl Config {
                     .clone()
                     .unwrap_or_else(|| self.data.cargo_features.clone())
                 {
-                    CargoFeatures::All => vec![],
-                    CargoFeatures::Listed(it) => it,
+                    CargoFeaturesDef::All => vec![],
+                    CargoFeaturesDef::Selected(it) => it,
                 },
                 extra_args: self.data.checkOnSave_extraArgs.clone(),
                 extra_env: self.check_on_save_extra_env(),
@@ -1564,10 +1581,10 @@ enum CallableCompletionDef {
 
 #[derive(Deserialize, Debug, Clone)]
 #[serde(untagged)]
-enum CargoFeatures {
+enum CargoFeaturesDef {
     #[serde(deserialize_with = "de_unit_v::all")]
     All,
-    Listed(Vec),
+    Selected(Vec),
 }
 
 #[derive(Deserialize, Debug, Clone)]
@@ -1912,7 +1929,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 "Only show mutable reborrow hints."
             ]
         },
-        "CargoFeatures" => set! {
+        "CargoFeaturesDef" => set! {
             "anyOf": [
                 {
                     "type": "string",
@@ -1929,7 +1946,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 }
             ],
         },
-        "Option" => set! {
+        "Option" => set! {
             "anyOf": [
                 {
                     "type": "string",
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs
index 7bdd34d1f0..f2db9a2733 100644
--- a/crates/rust-analyzer/src/from_proto.rs
+++ b/crates/rust-analyzer/src/from_proto.rs
@@ -95,22 +95,22 @@ pub(crate) fn annotation(
 
     match resolve {
         lsp_ext::CodeLensResolveData::Impls(params) => {
-            let file_id =
-                snap.url_to_file_id(¶ms.text_document_position_params.text_document.uri)?;
+            let pos @ FilePosition { file_id, .. } =
+                file_position(snap, params.text_document_position_params)?;
             let line_index = snap.file_line_index(file_id)?;
 
             Ok(Annotation {
                 range: text_range(&line_index, code_lens.range)?,
-                kind: AnnotationKind::HasImpls { file_id, data: None },
+                kind: AnnotationKind::HasImpls { pos, data: None },
             })
         }
         lsp_ext::CodeLensResolveData::References(params) => {
-            let file_id = snap.url_to_file_id(¶ms.text_document.uri)?;
+            let pos @ FilePosition { file_id, .. } = file_position(snap, params)?;
             let line_index = snap.file_line_index(file_id)?;
 
             Ok(Annotation {
                 range: text_range(&line_index, code_lens.range)?,
-                kind: AnnotationKind::HasReferences { file_id, data: None },
+                kind: AnnotationKind::HasReferences { pos, data: None },
             })
         }
     }
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 92df4d70fd..000ff88e45 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -185,11 +185,48 @@ impl GlobalState {
         let (change, changed_files) = {
             let mut change = Change::new();
             let (vfs, line_endings_map) = &mut *self.vfs.write();
-            let changed_files = vfs.take_changes();
+            let mut changed_files = vfs.take_changes();
             if changed_files.is_empty() {
                 return false;
             }
 
+            // important: this needs to be a stable sort, the order between changes is relevant
+            // for the same file ids
+            changed_files.sort_by_key(|file| file.file_id);
+            // We need to fix up the changed events a bit, if we have a create or modify for a file
+            // id that is followed by a delete we actually no longer observe the file text from the
+            // create or modify which may cause problems later on
+            changed_files.dedup_by(|a, b| {
+                use vfs::ChangeKind::*;
+
+                if a.file_id != b.file_id {
+                    return false;
+                }
+
+                match (a.change_kind, b.change_kind) {
+                    // duplicate can be merged
+                    (Create, Create) | (Modify, Modify) | (Delete, Delete) => true,
+                    // just leave the create, modify is irrelevant
+                    (Create, Modify) => {
+                        std::mem::swap(a, b);
+                        true
+                    }
+                    // modify becomes irrelevant if the file is deleted
+                    (Modify, Delete) => true,
+                    // we should fully remove this occurrence,
+                    // but leaving just a delete works as well
+                    (Create, Delete) => true,
+                    // this is equivalent to a modify
+                    (Delete, Create) => {
+                        a.change_kind = Modify;
+                        true
+                    }
+                    // can't really occur
+                    (Modify, Create) => false,
+                    (Delete, Modify) => false,
+                }
+            });
+
             for file in &changed_files {
                 if let Some(path) = vfs.file_path(file.file_id).as_path() {
                     let path = path.to_path_buf();
@@ -317,6 +354,10 @@ impl GlobalState {
         }
     }
 
+    pub(crate) fn is_completed(&self, request: &lsp_server::Request) -> bool {
+        self.req_queue.incoming.is_completed(&request.id)
+    }
+
     fn send(&mut self, message: lsp_server::Message) {
         self.sender.send(message).unwrap()
     }
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 3cfbc2e4e4..15922dac65 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -425,7 +425,9 @@ impl GlobalState {
     fn handle_task(&mut self, prime_caches_progress: &mut Vec, task: Task) {
         match task {
             Task::Response(response) => self.respond(response),
-            Task::Retry(req) => self.on_request(req),
+            // Only retry requests that haven't been cancelled. Otherwise we do unnecessary work.
+            Task::Retry(req) if !self.is_completed(&req) => self.on_request(req),
+            Task::Retry(_) => (),
             Task::Diagnostics(diagnostics_per_file) => {
                 for (file_id, diagnostics) in diagnostics_per_file {
                     self.diagnostics.set_native_diagnostics(file_id, diagnostics)
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 4cf5de46c4..f873489394 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -143,7 +143,7 @@ impl GlobalState {
                             project_model::ProjectWorkspace::load_inline(
                                 it.clone(),
                                 cargo_config.target.as_deref(),
-                                &cargo_config,
+                                &cargo_config.extra_env,
                             )
                         }
                     })
@@ -306,41 +306,50 @@ impl GlobalState {
             format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
 
         if self.proc_macro_clients.is_empty() {
-            if let Some((path, args)) = self.config.proc_macro_srv() {
+            if let Some((path, path_manually_set)) = self.config.proc_macro_srv() {
                 tracing::info!("Spawning proc-macro servers");
                 self.proc_macro_clients = self
                     .workspaces
                     .iter()
                     .map(|ws| {
-                        let mut args = args.clone();
-                        let mut path = path.clone();
-
-                        if let ProjectWorkspace::Cargo { sysroot, .. }
-                        | ProjectWorkspace::Json { sysroot, .. } = ws
-                        {
-                            tracing::debug!("Found a cargo workspace...");
-                            if let Some(sysroot) = sysroot.as_ref() {
-                                tracing::debug!("Found a cargo workspace with a sysroot...");
-                                let server_path =
-                                    sysroot.root().join("libexec").join(&standalone_server_name);
-                                if std::fs::metadata(&server_path).is_ok() {
-                                    tracing::debug!(
-                                        "And the server exists at {}",
-                                        server_path.display()
-                                    );
-                                    path = server_path;
-                                    args = vec![];
-                                } else {
-                                    tracing::debug!(
-                                        "And the server does not exist at {}",
-                                        server_path.display()
-                                    );
+                        let (path, args) = if path_manually_set {
+                            tracing::debug!(
+                                "Pro-macro server path explicitly set: {}",
+                                path.display()
+                            );
+                            (path.clone(), vec![])
+                        } else {
+                            let mut sysroot_server = None;
+                            if let ProjectWorkspace::Cargo { sysroot, .. }
+                            | ProjectWorkspace::Json { sysroot, .. } = ws
+                            {
+                                if let Some(sysroot) = sysroot.as_ref() {
+                                    let server_path = sysroot
+                                        .root()
+                                        .join("libexec")
+                                        .join(&standalone_server_name);
+                                    if std::fs::metadata(&server_path).is_ok() {
+                                        tracing::debug!(
+                                            "Sysroot proc-macro server exists at {}",
+                                            server_path.display()
+                                        );
+                                        sysroot_server = Some(server_path);
+                                    } else {
+                                        tracing::debug!(
+                                            "Sysroot proc-macro server does not exist at {}",
+                                            server_path.display()
+                                        );
+                                    }
                                 }
                             }
-                        }
+                            sysroot_server.map_or_else(
+                                || (path.clone(), vec!["proc-macro".to_owned()]),
+                                |path| (path, vec![]),
+                            )
+                        };
 
                         tracing::info!(?args, "Using proc-macro server at {}", path.display(),);
-                        ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|err| {
+                        ProcMacroServer::spawn(path.clone(), args).map_err(|err| {
                             let error = format!(
                                 "Failed to run proc-macro server from path {}, error: {:?}",
                                 path.display(),
@@ -402,7 +411,7 @@ impl GlobalState {
                 crate_graph.extend(ws.to_crate_graph(
                     &mut load_proc_macro,
                     &mut load,
-                    &self.config.cargo(),
+                    &self.config.cargo().extra_env,
                 ));
             }
             crate_graph
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 1de801e23e..5936454a7c 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1177,13 +1177,13 @@ pub(crate) fn code_lens(
                 })
             }
         }
-        AnnotationKind::HasImpls { file_id, data } => {
+        AnnotationKind::HasImpls { pos: file_range, data } => {
             if !client_commands_config.show_reference {
                 return Ok(());
             }
-            let line_index = snap.file_line_index(file_id)?;
+            let line_index = snap.file_line_index(file_range.file_id)?;
             let annotation_range = range(&line_index, annotation.range);
-            let url = url(snap, file_id);
+            let url = url(snap, file_range.file_id);
 
             let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
 
@@ -1221,13 +1221,13 @@ pub(crate) fn code_lens(
                 data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()),
             })
         }
-        AnnotationKind::HasReferences { file_id, data } => {
+        AnnotationKind::HasReferences { pos: file_range, data } => {
             if !client_commands_config.show_reference {
                 return Ok(());
             }
-            let line_index = snap.file_line_index(file_id)?;
+            let line_index = snap.file_line_index(file_range.file_id)?;
             let annotation_range = range(&line_index, annotation.range);
-            let url = url(snap, file_id);
+            let url = url(snap, file_range.file_id);
 
             let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
 
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 4cc46af1b1..fa55f7d90c 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -18,7 +18,6 @@ mod tidy;
 
 use std::{collections::HashMap, path::PathBuf, time::Instant};
 
-use expect_test::expect;
 use lsp_types::{
     notification::DidOpenTextDocument,
     request::{
@@ -60,7 +59,7 @@ use std::collections::Spam;
 "#,
     )
     .with_config(serde_json::json!({
-        "cargo": { "noSysroot": false }
+        "cargo": { "sysroot": "discover" }
     }))
     .server()
     .wait_until_workspace_is_loaded();
@@ -615,7 +614,7 @@ fn main() {{}}
         librs, libs
     ))
     .with_config(serde_json::json!({
-        "cargo": { "noSysroot": false }
+        "cargo": { "sysroot": "discover" }
     }))
     .server()
     .wait_until_workspace_is_loaded();
@@ -743,7 +742,7 @@ fn main() {
             "buildScripts": {
                 "enable": true
             },
-            "noSysroot": true,
+            "sysroot": null,
         }
     }))
     .server()
@@ -821,7 +820,10 @@ fn main() {
 }
 
 #[test]
+// FIXME: Re-enable once we can run proc-macro tests on rust-lang/rust-analyzer again
+#[cfg(any())]
 fn resolve_proc_macro() {
+    use expect_test::expect;
     if skip_slow_tests() {
         return;
     }
@@ -898,7 +900,7 @@ pub fn foo(_input: TokenStream) -> TokenStream {
             "buildScripts": {
                 "enable": true
             },
-            "noSysroot": true,
+            "sysroot": null,
         },
         "procMacro": {
             "enable": true,
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 4fa88c3c6d..7257445dab 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -34,7 +34,7 @@ impl<'a> Project<'a> {
             config: serde_json::json!({
                 "cargo": {
                     // Loading standard library is costly, let's ignore it by default
-                    "noSysroot": true,
+                    "sysroot": null,
                     // Can't use test binary as rustc wrapper.
                     "buildScripts": {
                         "useRustcWrapper": false
diff --git a/crates/rust-analyzer/tests/slow-tests/tidy.rs b/crates/rust-analyzer/tests/slow-tests/tidy.rs
index 58099a58de..24e68eca67 100644
--- a/crates/rust-analyzer/tests/slow-tests/tidy.rs
+++ b/crates/rust-analyzer/tests/slow-tests/tidy.rs
@@ -177,6 +177,7 @@ fn check_licenses() {
     let sh = &Shell::new().unwrap();
 
     let expected = "
+(MIT OR Apache-2.0) AND Unicode-DFS-2016
 0BSD OR MIT OR Apache-2.0
 Apache-2.0
 Apache-2.0 OR BSL-1.0
diff --git a/crates/sourcegen/Cargo.toml b/crates/sourcegen/Cargo.toml
index a84110d940..e75867e2d8 100644
--- a/crates/sourcegen/Cargo.toml
+++ b/crates/sourcegen/Cargo.toml
@@ -11,6 +11,3 @@ doctest = false
 
 [dependencies]
 xshell = "0.2.2"
-
-[features]
-in-rust-tree = []
diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml
index 092b99ae51..e0657ab0f6 100644
--- a/crates/stdx/Cargo.toml
+++ b/crates/stdx/Cargo.toml
@@ -10,7 +10,7 @@ rust-version = "1.57"
 doctest = false
 
 [dependencies]
-libc = "0.2.126"
+libc = "0.2.135"
 backtrace = { version = "0.3.65", optional = true }
 always-assert = { version = "0.1.2", features = ["log"] }
 # Think twice before adding anything here
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 0e2dec386f..1ef903371c 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -12,11 +12,11 @@ doctest = false
 
 [dependencies]
 cov-mark = "2.0.0-pre.1"
-itertools = "0.10.3"
-rowan = "0.15.8"
+itertools = "0.10.5"
+rowan = "0.15.10"
 rustc_lexer = { version = "725.0.0", package = "rustc-ap-rustc_lexer" }
 rustc-hash = "1.1.0"
-once_cell = "1.12.0"
+once_cell = "1.15.0"
 indexmap = "1.9.1"
 smol_str = "0.1.23"
 
@@ -28,7 +28,7 @@ profile = { path = "../profile", version = "0.0.0" }
 [dev-dependencies]
 rayon = "1.5.3"
 expect-test = "1.4.0"
-proc-macro2 = "1.0.39"
+proc-macro2 = "1.0.47"
 quote = "1.0.20"
 ungrammar = "1.16.1"
 
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index eadebbe8a2..229e7419b7 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -235,6 +235,24 @@ impl ast::GenericParamList {
             }
         }
     }
+
+    /// Constructs a matching [`ast::GenericArgList`]
+    pub fn to_generic_args(&self) -> ast::GenericArgList {
+        let args = self.generic_params().filter_map(|param| match param {
+            ast::GenericParam::LifetimeParam(it) => {
+                Some(ast::GenericArg::LifetimeArg(make::lifetime_arg(it.lifetime()?)))
+            }
+            ast::GenericParam::TypeParam(it) => {
+                Some(ast::GenericArg::TypeArg(make::type_arg(make::ext::ty_name(it.name()?))))
+            }
+            ast::GenericParam::ConstParam(it) => {
+                // Name-only const params get parsed as `TypeArg`s
+                Some(ast::GenericArg::TypeArg(make::type_arg(make::ext::ty_name(it.name()?))))
+            }
+        });
+
+        make::generic_arg_list(args)
+    }
 }
 
 impl ast::WhereClause {
@@ -248,6 +266,42 @@ impl ast::WhereClause {
     }
 }
 
+impl ast::TypeParam {
+    pub fn remove_default(&self) {
+        if let Some((eq, last)) = self
+            .syntax()
+            .children_with_tokens()
+            .find(|it| it.kind() == T![=])
+            .zip(self.syntax().last_child_or_token())
+        {
+            ted::remove_all(eq..=last);
+
+            // remove any trailing ws
+            if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) {
+                last.detach();
+            }
+        }
+    }
+}
+
+impl ast::ConstParam {
+    pub fn remove_default(&self) {
+        if let Some((eq, last)) = self
+            .syntax()
+            .children_with_tokens()
+            .find(|it| it.kind() == T![=])
+            .zip(self.syntax().last_child_or_token())
+        {
+            ted::remove_all(eq..=last);
+
+            // remove any trailing ws
+            if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) {
+                last.detach();
+            }
+        }
+    }
+}
+
 pub trait Removable: AstNode {
     fn remove(&self);
 }
@@ -264,7 +318,7 @@ impl Removable for ast::TypeBoundList {
 impl ast::PathSegment {
     pub fn get_or_create_generic_arg_list(&self) -> ast::GenericArgList {
         if self.generic_arg_list().is_none() {
-            let arg_list = make::generic_arg_list().clone_for_update();
+            let arg_list = make::generic_arg_list(empty()).clone_for_update();
             ted::append_child(self.syntax(), arg_list.syntax());
         }
         self.generic_arg_list().unwrap()
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 83f8bbac58..4057a75e7c 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -88,6 +88,9 @@ pub mod ext {
         block_expr(None, None)
     }
 
+    pub fn ty_name(name: ast::Name) -> ast::Type {
+        ty_path(ident_path(&name.to_string()))
+    }
     pub fn ty_bool() -> ast::Type {
         ty_path(ident_path("bool"))
     }
@@ -160,6 +163,7 @@ pub fn assoc_item_list() -> ast::AssocItemList {
     ast_from_text("impl C for D {}")
 }
 
+// FIXME: `ty_params` should be `ast::GenericArgList`
 pub fn impl_(
     ty: ast::Path,
     params: Option,
@@ -185,10 +189,6 @@ pub fn impl_trait(
     ast_from_text(&format!("impl{ty_params} {trait_} for {ty}{ty_params} {{}}"))
 }
 
-pub(crate) fn generic_arg_list() -> ast::GenericArgList {
-    ast_from_text("const S: T<> = ();")
-}
-
 pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
     ast_from_text(&format!("type __ = {name_ref};"))
 }
@@ -718,6 +718,21 @@ pub fn generic_param_list(
     ast_from_text(&format!("fn f<{args}>() {{ }}"))
 }
 
+pub fn type_arg(ty: ast::Type) -> ast::TypeArg {
+    ast_from_text(&format!("const S: T<{ty}> = ();"))
+}
+
+pub fn lifetime_arg(lifetime: ast::Lifetime) -> ast::LifetimeArg {
+    ast_from_text(&format!("const S: T<{lifetime}> = ();"))
+}
+
+pub(crate) fn generic_arg_list(
+    args: impl IntoIterator,
+) -> ast::GenericArgList {
+    let args = args.into_iter().join(", ");
+    ast_from_text(&format!("const S: T<{args}> = ();"))
+}
+
 pub fn visibility_pub_crate() -> ast::Visibility {
     ast_from_text("pub(crate) struct S")
 }
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index bb92c51e9a..fe82aa9072 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -873,3 +873,33 @@ impl ast::MatchGuard {
         support::child(&self.syntax)
     }
 }
+
+impl From for ast::AnyHasAttrs {
+    fn from(node: ast::Item) -> Self {
+        Self::new(node)
+    }
+}
+
+impl From for ast::AnyHasAttrs {
+    fn from(node: ast::AssocItem) -> Self {
+        Self::new(node)
+    }
+}
+
+impl From for ast::AnyHasAttrs {
+    fn from(node: ast::Variant) -> Self {
+        Self::new(node)
+    }
+}
+
+impl From for ast::AnyHasAttrs {
+    fn from(node: ast::RecordField) -> Self {
+        Self::new(node)
+    }
+}
+
+impl From for ast::AnyHasAttrs {
+    fn from(node: ast::TupleField) -> Self {
+        Self::new(node)
+    }
+}
diff --git a/crates/test-utils/src/fixture.rs b/crates/test-utils/src/fixture.rs
index 8c806e7925..c824f5af72 100644
--- a/crates/test-utils/src/fixture.rs
+++ b/crates/test-utils/src/fixture.rs
@@ -61,6 +61,8 @@
 //! "
 //! ```
 
+use std::iter;
+
 use rustc_hash::FxHashMap;
 use stdx::trim_indent;
 
@@ -259,7 +261,7 @@ impl MiniCore {
             if res.has_flag(entry) {
                 panic!("duplicate minicore flag: {:?}", entry);
             }
-            res.activated_flags.push(entry.to_string());
+            res.activated_flags.push(entry.to_owned());
         }
 
         res
@@ -273,35 +275,34 @@ impl MiniCore {
         let raw_mini_core = include_str!("./minicore.rs");
         let mut lines = raw_mini_core.split_inclusive('\n');
 
-        let mut parsing_flags = false;
         let mut implications = Vec::new();
 
         // Parse `//!` preamble and extract flags and dependencies.
-        for line in lines.by_ref() {
-            let line = match line.strip_prefix("//!") {
-                Some(it) => it,
-                None => {
-                    assert!(line.trim().is_empty());
-                    break;
-                }
-            };
-
-            if parsing_flags {
-                let (flag, deps) = line.split_once(':').unwrap();
-                let flag = flag.trim();
-                self.valid_flags.push(flag.to_string());
-                for dep in deps.split(", ") {
-                    let dep = dep.trim();
-                    if !dep.is_empty() {
-                        self.assert_valid_flag(dep);
-                        implications.push((flag, dep));
-                    }
-                }
+        let trim_doc: fn(&str) -> Option<&str> = |line| match line.strip_prefix("//!") {
+            Some(it) => Some(it),
+            None => {
+                assert!(line.trim().is_empty(), "expected empty line after minicore header");
+                None
             }
+        };
+        for line in lines
+            .by_ref()
+            .map_while(trim_doc)
+            .skip_while(|line| !line.contains("Available flags:"))
+            .skip(1)
+        {
+            let (flag, deps) = line.split_once(':').unwrap();
+            let flag = flag.trim();
 
-            if line.contains("Available flags:") {
-                parsing_flags = true;
-            }
+            self.valid_flags.push(flag.to_string());
+            implications.extend(
+                iter::repeat(flag)
+                    .zip(deps.split(", ").map(str::trim).filter(|dep| !dep.is_empty())),
+            );
+        }
+
+        for (_, dep) in &implications {
+            self.assert_valid_flag(dep);
         }
 
         for flag in &self.activated_flags {
@@ -332,7 +333,7 @@ impl MiniCore {
             }
             if let Some(region) = trimmed.strip_prefix("// endregion:") {
                 let prev = active_regions.pop().unwrap();
-                assert_eq!(prev, region);
+                assert_eq!(prev, region, "unbalanced region pairs");
                 continue;
             }
 
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 6df29db474..59b1c147d7 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -8,35 +8,37 @@
 //! We then strip all the code marked with other flags.
 //!
 //! Available flags:
-//!     sized:
-//!     unsize: sized
-//!     coerce_unsized: unsize
-//!     slice:
-//!     range:
-//!     deref: sized
-//!     deref_mut: deref
-//!     index: sized
-//!     fn:
-//!     try:
-//!     pin:
-//!     future: pin
-//!     option:
-//!     result:
-//!     iterator: option
-//!     iterators: iterator, fn
-//!     default: sized
-//!     hash:
-//!     clone: sized
-//!     copy: clone
-//!     from: sized
-//!     eq: sized
-//!     ord: eq, option
-//!     derive:
-//!     fmt: result
-//!     bool_impl: option, fn
 //!     add:
 //!     as_ref: sized
+//!     bool_impl: option, fn
+//!     clone: sized
+//!     coerce_unsized: unsize
+//!     copy: clone
+//!     default: sized
+//!     deref_mut: deref
+//!     deref: sized
+//!     derive:
 //!     drop:
+//!     eq: sized
+//!     fmt: result
+//!     fn:
+//!     from: sized
+//!     future: pin
+//!     generator: pin
+//!     hash:
+//!     index: sized
+//!     infallible:
+//!     iterator: option
+//!     iterators: iterator, fn
+//!     option:
+//!     ord: eq, option
+//!     pin:
+//!     range:
+//!     result:
+//!     sized:
+//!     slice:
+//!     try: infallible
+//!     unsize: sized
 
 pub mod marker {
     // region:sized
@@ -149,6 +151,9 @@ pub mod convert {
         fn as_ref(&self) -> &T;
     }
     // endregion:as_ref
+    // region:infallible
+    pub enum Infallible {}
+    // endregion:infallible
 }
 
 pub mod ops {
@@ -182,6 +187,19 @@ pub mod ops {
             type Target: ?Sized;
             fn deref(&self) -> &Self::Target;
         }
+
+        impl Deref for &T {
+            type Target = T;
+            fn deref(&self) -> &T {
+                loop {}
+            }
+        }
+        impl Deref for &mut T {
+            type Target = T;
+            fn deref(&self) -> &T {
+                loop {}
+            }
+        }
         // region:deref_mut
         #[lang = "deref_mut"]
         pub trait DerefMut: Deref {
@@ -312,7 +330,7 @@ pub mod ops {
             Continue(C),
             Break(B),
         }
-        pub trait FromResidual {
+        pub trait FromResidual::Residual> {
             #[lang = "from_residual"]
             fn from_residual(residual: R) -> Self;
         }
@@ -328,13 +346,13 @@ pub mod ops {
 
         impl Try for ControlFlow {
             type Output = C;
-            type Residual = ControlFlow;
+            type Residual = ControlFlow;
             fn from_output(output: Self::Output) -> Self {}
             fn branch(self) -> ControlFlow {}
         }
 
         impl FromResidual for ControlFlow {
-            fn from_residual(residual: ControlFlow) -> Self {}
+            fn from_residual(residual: ControlFlow) -> Self {}
         }
     }
     pub use self::try_::{ControlFlow, FromResidual, Try};
@@ -347,6 +365,27 @@ pub mod ops {
         fn add(self, rhs: Rhs) -> Self::Output;
     }
     // endregion:add
+
+    // region:generator
+    mod generator {
+        use crate::pin::Pin;
+
+        #[lang = "generator"]
+        pub trait Generator {
+            type Yield;
+            #[lang = "generator_return"]
+            type Return;
+            fn resume(self: Pin<&mut Self>, arg: R) -> GeneratorState;
+        }
+
+        #[lang = "generator_state"]
+        pub enum GeneratorState {
+            Yielded(Y),
+            Complete(R),
+        }
+    }
+    pub use self::generator::{Generator, GeneratorState};
+    // endregion:generator
 }
 
 // region:eq
@@ -434,6 +473,33 @@ pub mod option {
             }
         }
     }
+    // region:try
+    impl crate::ops::Try for Option {
+        type Output = T;
+        type Residual = Option;
+
+        #[inline]
+        fn from_output(output: Self::Output) -> Self {
+            Some(output)
+        }
+
+        #[inline]
+        fn branch(self) -> crate::ops::ControlFlow {
+            match self {
+                Some(v) => crate::ops::ControlFlow::Continue(v),
+                None => crate::ops::ControlFlow::Break(None),
+            }
+        }
+    }
+    impl crate::ops::FromResidual for Option {
+        #[inline]
+        fn from_residual(residual: Option) -> Self {
+            match residual {
+                None => None,
+            }
+        }
+    }
+    // endregion:try
 }
 // endregion:option
 
@@ -455,6 +521,19 @@ pub mod pin {
     pub struct Pin

{ pointer: P, } + impl

Pin

{ + pub fn new(pointer: P) -> Pin

{ + loop {} + } + } + // region:deref + impl crate::ops::Deref for Pin

{ + type Target = P::Target; + fn deref(&self) -> &P::Target { + loop {} + } + } + // endregion:deref } // endregion:pin @@ -536,7 +615,7 @@ pub mod iter { } } } - pub use self::adapters::{Take, FilterMap}; + pub use self::adapters::{FilterMap, Take}; mod sources { mod repeat { diff --git a/crates/text-edit/Cargo.toml b/crates/text-edit/Cargo.toml index cf14bbd3c3..7a90d64a98 100644 --- a/crates/text-edit/Cargo.toml +++ b/crates/text-edit/Cargo.toml @@ -10,5 +10,5 @@ rust-version = "1.57" doctest = false [dependencies] -itertools = "0.10.3" +itertools = "0.10.5" text-size = "1.1.0" diff --git a/crates/toolchain/Cargo.toml b/crates/toolchain/Cargo.toml index 7d3b9e09ec..3e0f31f19c 100644 --- a/crates/toolchain/Cargo.toml +++ b/crates/toolchain/Cargo.toml @@ -10,4 +10,4 @@ rust-version = "1.57" doctest = false [dependencies] -home = "0.5.3" +home = "0.5.4" diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml index fcc693a7dd..df5dc24e2c 100644 --- a/crates/vfs-notify/Cargo.toml +++ b/crates/vfs-notify/Cargo.toml @@ -14,7 +14,7 @@ tracing = "0.1.35" jod-thread = "0.1.2" walkdir = "2.3.2" crossbeam-channel = "0.5.5" -notify = "=5.0.0-pre.16" +notify = "5.0" vfs = { path = "../vfs", version = "0.0.0" } paths = { path = "../paths", version = "0.0.0" } diff --git a/docs/dev/README.md b/docs/dev/README.md index c7f152acc2..4ac75b4bbf 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -98,7 +98,7 @@ After I am done with the fix, I use `cargo xtask install --client` to try the ne If I need to fix something in the `rust-analyzer` crate, I feel sad because it's on the boundary between the two processes, and working there is slow. I usually just `cargo xtask install --server` and poke changes from my live environment. Note that this uses `--release`, which is usually faster overall, because loading stdlib into debug version of rust-analyzer takes a lot of time. -To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.cargo.noSysroot": true` in `.code/settings.json`. +To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.cargo.sysroot": null` in `.code/settings.json`. This flag causes rust-analyzer to skip loading the sysroot, which greatly reduces the amount of things rust-analyzer needs to do, and makes printf's more useful. Note that you should only use the `eprint!` family of macros for debugging: stdout is used for LSP communication, and `print!` would break it. diff --git a/docs/dev/guide.md b/docs/dev/guide.md index 808eb5d10b..52a13da31c 100644 --- a/docs/dev/guide.md +++ b/docs/dev/guide.md @@ -40,8 +40,8 @@ terms of files and offsets, and **not** in terms of Rust concepts like structs, traits, etc. The "typed" API with Rust specific types is slightly lower in the stack, we'll talk about it later. -[`AnalysisHost`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L265-L284 -[`Analysis`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L291-L478 +[`AnalysisHost`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L265-L284 +[`Analysis`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L291-L478 The reason for this separation of `Analysis` and `AnalysisHost` is that we want to apply changes "uniquely", but we might also want to fork an `Analysis` and send it to @@ -88,9 +88,8 @@ is lower than Cargo's model of packages: each Cargo package consists of several targets, each of which is a separate crate (or several crates, if you try different feature combinations). -Procedural macros should become inputs as well, but currently they are not -supported. Procedural macro will be a black box `Box TokenStream>` -function, and will be inserted into the crate graph just like dependencies. +Procedural macros are inputs as well, roughly modeled as a crate with a bunch of +additional black box `dyn Fn(TokenStream) -> TokenStream` functions. Soon we'll talk how we build an LSP server on top of `Analysis`, but first, let's deal with that paths issue. diff --git a/docs/dev/syntax.md b/docs/dev/syntax.md index 30e1370138..97e376787c 100644 --- a/docs/dev/syntax.md +++ b/docs/dev/syntax.md @@ -8,10 +8,10 @@ This guide describes the current state of syntax trees and parsing in rust-analy The things described are implemented in three places -* [rowan](https://github.com/rust-analyzer/rowan/tree/v0.9.0) -- a generic library for rowan syntax trees. -* [ra_syntax](https://github.com/rust-lang/rust-analyzer/tree/cf5bdf464cad7ceb9a67e07985a3f4d3799ec0b6/crates/ra_syntax) crate inside rust-analyzer which wraps `rowan` into rust-analyzer specific API. +* [rowan](https://github.com/rust-analyzer/rowan/tree/v0.15.10) -- a generic library for rowan syntax trees. +* [syntax](https://github.com/rust-lang/rust-analyzer/tree/36a70b7435c48837018c71576d7bb4e8f763f501/crates/syntax) crate inside rust-analyzer which wraps `rowan` into rust-analyzer specific API. Nothing in rust-analyzer except this crate knows about `rowan`. -* [parser](https://github.com/rust-lang/rust-analyzer/tree/cf5bdf464cad7ceb9a67e07985a3f4d3799ec0b6/crates/parser) crate parses input tokens into an `ra_syntax` tree +* [parser](https://github.com/rust-lang/rust-analyzer/tree/36a70b7435c48837018c71576d7bb4e8f763f501/crates/parser) crate parses input tokens into a `syntax` tree ## Design Goals diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 996d4c023d..acf0aaea85 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -64,10 +64,15 @@ Set this to `"all"` to pass `--all-features` to cargo. -- Whether to pass `--no-default-features` to cargo. -- -[[rust-analyzer.cargo.noSysroot]]rust-analyzer.cargo.noSysroot (default: `false`):: +[[rust-analyzer.cargo.sysroot]]rust-analyzer.cargo.sysroot (default: `"discover"`):: + -- -Internal config for debugging, disables loading of sysroot crates. +Relative path to the sysroot, or "discover" to try to automatically find it via +"rustc --print sysroot". + +Unsetting this disables sysroot loading. + +This option does not take effect until rust-analyzer is restarted. -- [[rust-analyzer.cargo.target]]rust-analyzer.cargo.target (default: `null`):: + @@ -103,6 +108,7 @@ Extra arguments for `cargo check`. + -- Extra environment variables that will be set when running `cargo check`. +Extends `#rust-analyzer.cargo.extraEnv#`. -- [[rust-analyzer.checkOnSave.features]]rust-analyzer.checkOnSave.features (default: `null`):: + diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 9bd3b6a692..c30838e5f5 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -174,14 +174,25 @@ On Unix, running the editor from a shell or changing the `.desktop` file to set ==== `rustup` -`rust-analyzer` is available in `rustup`, but only in the nightly toolchain: +`rust-analyzer` is available in `rustup`: [source,bash] ---- -$ rustup +nightly component add rust-analyzer-preview +$ rustup component add rust-analyzer ---- -However, in contrast to `component add clippy` or `component add rustfmt`, this does not actually place a `rust-analyzer` binary in `~/.cargo/bin`, see https://github.com/rust-lang/rustup/issues/2411[this issue]. +However, in contrast to `component add clippy` or `component add rustfmt`, this does not actually place a `rust-analyzer` binary in `~/.cargo/bin`, see https://github.com/rust-lang/rustup/issues/2411[this issue]. You can find the path to the binary using: +[source,bash] +---- +$ rustup which --toolchain stable rust-analyzer +---- +You can link to there from `~/.cargo/bin` or configure your editor to use the full path. + +Alternatively you might be able to configure your editor to start `rust-analyzer` using the command: +[source,bash] +---- +$ rustup run stable rust-analyzer +---- ==== Arch Linux diff --git a/editors/code/package.json b/editors/code/package.json index 94b41c049b..f1dd3aa79f 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -468,10 +468,13 @@ "default": false, "type": "boolean" }, - "rust-analyzer.cargo.noSysroot": { - "markdownDescription": "Internal config for debugging, disables loading of sysroot crates.", - "default": false, - "type": "boolean" + "rust-analyzer.cargo.sysroot": { + "markdownDescription": "Relative path to the sysroot, or \"discover\" to try to automatically find it via\n\"rustc --print sysroot\".\n\nUnsetting this disables sysroot loading.\n\nThis option does not take effect until rust-analyzer is restarted.", + "default": "discover", + "type": [ + "null", + "string" + ] }, "rust-analyzer.cargo.target": { "markdownDescription": "Compilation target override (target triple).", @@ -515,7 +518,7 @@ } }, "rust-analyzer.checkOnSave.extraEnv": { - "markdownDescription": "Extra environment variables that will be set when running `cargo check`.", + "markdownDescription": "Extra environment variables that will be set when running `cargo check`.\nExtends `#rust-analyzer.cargo.extraEnv#`.", "default": {}, "type": "object" }, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index a9c0f079b3..15846a5e86 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -133,7 +133,21 @@ export class Config { } get runnableEnv() { - return this.get("runnableEnv"); + const item = this.get("runnableEnv"); + if (!item) return item; + const fixRecord = (r: Record) => { + for (const key in r) { + if (typeof r[key] !== "string") { + r[key] = String(r[key]); + } + } + }; + if (item instanceof Array) { + item.forEach((x) => fixRecord(x.env)); + } else { + fixRecord(item); + } + return item; } get restartServerOnConfigChange() { diff --git a/lib/lsp-server/Cargo.toml b/lib/lsp-server/Cargo.toml index 204d120d07..5922bbfdb4 100644 --- a/lib/lsp-server/Cargo.toml +++ b/lib/lsp-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lsp-server" -version = "0.6.0" +version = "0.7.0" description = "Generic LSP server scaffold." license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/lsp-server" @@ -8,9 +8,9 @@ edition = "2021" [dependencies] log = "0.4.17" -serde_json = "1.0.81" -serde = { version = "1.0.137", features = ["derive"] } -crossbeam-channel = "0.5.5" +serde_json = "1.0.86" +serde = { version = "1.0.144", features = ["derive"] } +crossbeam-channel = "0.5.6" [dev-dependencies] -lsp-types = "0.93.0" +lsp-types = "0.93.1" diff --git a/lib/lsp-server/src/msg.rs b/lib/lsp-server/src/msg.rs index 97e5bd35ce..b241561f9c 100644 --- a/lib/lsp-server/src/msg.rs +++ b/lib/lsp-server/src/msg.rs @@ -98,7 +98,7 @@ pub struct ResponseError { } #[derive(Clone, Copy, Debug)] -#[allow(unused)] +#[non_exhaustive] pub enum ErrorCode { // Defined by JSON RPC: ParseError = -32700, @@ -135,6 +135,14 @@ pub enum ErrorCode { /// /// @since 3.17.0 ServerCancelled = -32802, + + /// A request failed but it was syntactically correct, e.g the + /// method name was known and the parameters were valid. The error + /// message should contain human readable information about why + /// the request failed. + /// + /// @since 3.17.0 + RequestFailed = -32803, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/lib/lsp-server/src/req_queue.rs b/lib/lsp-server/src/req_queue.rs index 1f3d447153..e5f19be20b 100644 --- a/lib/lsp-server/src/req_queue.rs +++ b/lib/lsp-server/src/req_queue.rs @@ -35,6 +35,7 @@ impl Incoming { pub fn register(&mut self, id: RequestId, data: I) { self.pending.insert(id, data); } + pub fn cancel(&mut self, id: RequestId) -> Option { let _data = self.complete(id.clone())?; let error = ResponseError { @@ -44,9 +45,14 @@ impl Incoming { }; Some(Response { id, result: None, error: Some(error) }) } + pub fn complete(&mut self, id: RequestId) -> Option { self.pending.remove(&id) } + + pub fn is_completed(&self, id: &RequestId) -> bool { + !self.pending.contains_key(id) + } } impl Outgoing { @@ -56,6 +62,7 @@ impl Outgoing { self.next_id += 1; Request::new(id, method, params) } + pub fn complete(&mut self, id: RequestId) -> Option { self.pending.remove(&id) } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 95d44e9b9d..0be0bf920d 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" rust-version = "1.57" [dependencies] -anyhow = "1.0.57" +anyhow = "1.0.62" flate2 = "1.0.24" write-json = "0.1.2" xshell = "0.2.2" -xflags = "0.2.4" +xflags = "0.3.0" # Avoid adding more dependencies to this crate diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs index 993c64ccea..0fce488983 100644 --- a/xtask/src/flags.rs +++ b/xtask/src/flags.rs @@ -7,10 +7,6 @@ xflags::xflags! { /// Run custom build command. cmd xtask { - default cmd help { - /// Print help information. - optional -h, --help - } /// Install rust-analyzer server or editor plugin. cmd install { @@ -42,9 +38,9 @@ xflags::xflags! { optional --dry-run } /// Builds a benchmark version of rust-analyzer and puts it into `./target`. - cmd bb + cmd bb { required suffix: String - {} + } } } @@ -58,7 +54,6 @@ pub struct Xtask { #[derive(Debug)] pub enum XtaskCmd { - Help(Help), Install(Install), FuzzTests(FuzzTests), Release(Release), @@ -68,11 +63,6 @@ pub enum XtaskCmd { Bb(Bb), } -#[derive(Debug)] -pub struct Help { - pub help: bool, -} - #[derive(Debug)] pub struct Install { pub client: bool, @@ -111,7 +101,10 @@ pub struct Bb { } impl Xtask { - pub const HELP: &'static str = Self::HELP_; + #[allow(dead_code)] + pub fn from_env_or_exit() -> Self { + Self::from_env_or_exit_() + } #[allow(dead_code)] pub fn from_env() -> xflags::Result { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 335ac324a5..a37f469adc 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -25,15 +25,12 @@ use std::{ use xshell::{cmd, Shell}; fn main() -> anyhow::Result<()> { + let flags = flags::Xtask::from_env_or_exit(); + let sh = &Shell::new()?; sh.change_dir(project_root()); - let flags = flags::Xtask::from_env()?; match flags.subcommand { - flags::XtaskCmd::Help(_) => { - println!("{}", flags::Xtask::HELP); - Ok(()) - } flags::XtaskCmd::Install(cmd) => cmd.run(sh), flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh), flags::XtaskCmd::Release(cmd) => cmd.run(sh),