diff --git a/src/tools/rust-analyzer/.github/actions/github-release/README.md b/src/tools/rust-analyzer/.github/actions/github-release/README.md index 7b50d002001e..c8ff3ec6e52c 100644 --- a/src/tools/rust-analyzer/.github/actions/github-release/README.md +++ b/src/tools/rust-analyzer/.github/actions/github-release/README.md @@ -10,7 +10,7 @@ perform github releases but they all tend to have their set of drawbacks. Additionally nothing handles deleting releases which we need for our rolling `dev` release. -To handle all this this action rolls-its-own implementation using the +To handle all this, this action rolls its own implementation using the actions/toolkit repository and packages published there. These run in a Docker container and take various inputs to orchestrate the release from the build. diff --git a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml index 279f86b458df..7090c94d93cc 100644 --- a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml +++ b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml @@ -32,7 +32,7 @@ jobs: shell: bash run: | git config --global user.email "runner@gha.local" - git config --global user.name "Github Action" + git config --global user.name "GitHub Action" rm Cargo.lock # Fix names for crates that were published before switch to kebab-case. cargo workspaces rename --from base-db base_db diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index bb77324378a4..622da105fdd4 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -18,12 +18,35 @@ env: RUSTUP_MAX_RETRIES: 10 jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + typescript: ${{ steps.filter.outputs.typescript }} + steps: + - uses: actions/checkout@v3 + - uses: dorny/paths-filter@4067d885736b84de7c414f582ac45897079b0a78 + id: filter + with: + filters: | + typescript: + - 'editors/code/**' + proc_macros: + - 'crates/proc-macro-api/**' + - 'crates/proc-macro-srv/**' + - 'crates/proc-macro-srv-cli/**' + - 'crates/proc-macro-test/**' + rust: + needs: changes if: github.repository == 'rust-lang/rust-analyzer' name: Rust runs-on: ${{ matrix.os }} env: CC: deny_c + RUST_CHANNEL: "${{ needs.changes.outputs.proc_macros == 'true' && 'nightly' || 'stable'}}" + USE_SYSROOT_ABI: "${{ needs.changes.outputs.proc_macros == 'true' && '--features sysroot-abi' || ''}}" strategy: fail-fast: false @@ -35,30 +58,31 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 20 - name: Install Rust toolchain run: | - rustup update --no-self-update stable + rustup update --no-self-update ${{ env.RUST_CHANNEL }} rustup component add rustfmt rust-src - name: Cache Dependencies - uses: Swatinem/rust-cache@76686c56f2b581d1bb5bda44b51f7e24bd9b8b8e + uses: Swatinem/rust-cache@988c164c3d0e93c4dbab36aaf5bbeb77425b2894 + with: + key: ${{ env.RUST_CHANNEL }} - 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 + run: cargo test --no-run --locked ${{ env.USE_SYSROOT_ABI }} # It's faster to `test` before `build` ¯\_(ツ)_/¯ - name: Compile (rust-analyzer) if: matrix.os == 'ubuntu-latest' - run: cargo build --quiet + run: cargo build --quiet ${{ env.USE_SYSROOT_ABI }} - name: Test - run: cargo test -- --nocapture --quiet + run: cargo test ${{ env.USE_SYSROOT_ABI }} -- --nocapture --quiet - name: Run analysis-stats on rust-analyzer if: matrix.os == 'ubuntu-latest' @@ -90,7 +114,7 @@ jobs: rustup target add ${{ env.targets }} ${{ env.targets_ide }} - name: Cache Dependencies - uses: Swatinem/rust-cache@76686c56f2b581d1bb5bda44b51f7e24bd9b8b8e + uses: Swatinem/rust-cache@988c164c3d0e93c4dbab36aaf5bbeb77425b2894 - name: Check run: | @@ -102,6 +126,7 @@ jobs: done typescript: + needs: changes if: github.repository == 'rust-lang/rust-analyzer' name: TypeScript strategy: @@ -114,18 +139,21 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + if: needs.changes.outputs.typescript == 'true' - name: Install Nodejs uses: actions/setup-node@v3 with: node-version: 16 + if: needs.changes.outputs.typescript == 'true' - name: Install xvfb - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true' run: sudo apt-get install -y xvfb - run: npm ci working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' # - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; } # if: runner.os == 'Linux' @@ -133,16 +161,17 @@ jobs: - run: npm run lint working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' - name: Run VS Code tests (Linux) - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true' env: VSCODE_CLI: 1 run: xvfb-run npm test working-directory: ./editors/code - name: Run VS Code tests (Windows) - if: matrix.os == 'windows-latest' + if: matrix.os == 'windows-latest' && needs.changes.outputs.typescript == 'true' env: VSCODE_CLI: 1 run: npm test @@ -150,9 +179,11 @@ jobs: - run: npm run pretest working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' - run: npm run package --scripts-prepend-node-path working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' end-success: name: bors build finished @@ -165,7 +196,7 @@ jobs: end-failure: name: bors build finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + if: github.event.pusher.name == 'bors' && !success() runs-on: ubuntu-latest needs: [rust, rust-cross, typescript] steps: diff --git a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml index 1b843fff1a4a..6d026c9ad910 100644 --- a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml +++ b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml @@ -3,9 +3,9 @@ on: workflow_dispatch: push: branches: - - main + - master paths: - - 'lib/**' + - "lib/**" jobs: publish-libs: @@ -29,7 +29,7 @@ jobs: shell: bash run: | git config --global user.email "runner@gha.local" - git config --global user.name "Github Action" + git config --global user.name "GitHub Action" # Remove r-a crates from the workspaces so we don't auto-publish them as well sed -i 's/ "crates\/\*"//' ./Cargo.toml cargo workspaces publish --yes --exact --from-git --no-git-commit --allow-dirty diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index 48f4c6b55ed9..43681c785fdc 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -270,7 +270,7 @@ jobs: - name: Publish Extension (Code Marketplace, nightly) if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') working-directory: ./editors/code - run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix + run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix --pre-release - name: Publish Extension (OpenVSX, nightly) if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') diff --git a/src/tools/rust-analyzer/.vscode/launch.json b/src/tools/rust-analyzer/.vscode/launch.json index 1e21214ffc4b..c353737a35a8 100644 --- a/src/tools/rust-analyzer/.vscode/launch.json +++ b/src/tools/rust-analyzer/.vscode/launch.json @@ -72,7 +72,7 @@ }, { // Used for testing the extension with a local build of the LSP server (in `target/release`) - // with all other extendions loaded. + // with all other extensions loaded. "name": "Run With Extensions", "type": "extensionHost", "request": "launch", diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 25242c6028a4..e36aef6a6aa8 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -19,18 +19,18 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "always-assert" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf688625d06217d5b1bb0ea9d9c44a1635fd0ee3534466388d18203174f4d11" +checksum = "4436e0292ab1bb631b42973c61205e704475fe8126af845c8d923c0996328127" dependencies = [ "log", ] [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "anymap" @@ -40,9 +40,9 @@ checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" [[package]] name = "arbitrary" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0224938f92e7aef515fac2ff2d18bd1115c1394ddf4a092e0c87e8be9499ee5" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" [[package]] name = "arrayvec" @@ -87,12 +87,14 @@ name = "base-db" version = "0.0.0" dependencies = [ "cfg", + "la-arena", "profile", "rustc-hash", "salsa", "stdx", "syntax", "test-utils", + "triomphe", "tt", "vfs", ] @@ -103,6 +105,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" + [[package]] name = "byteorder" version = "1.4.3" @@ -111,9 +119,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "camino" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" dependencies = [ "serde", ] @@ -129,9 +137,9 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", @@ -143,9 +151,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg" @@ -185,7 +193,7 @@ version = "0.89.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473b480241695428c14e8f84f1c9a47ef232450a50faf3a4041e5c9dc11e0a3b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "chalk-derive", "lazy_static", ] @@ -221,9 +229,9 @@ dependencies = [ [[package]] name = "command-group" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "026c3922235f9f7d78f21251a026f3acdeb7cce3deba107fe09a4bfa63d850a2" +checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" dependencies = [ "nix", "winapi", @@ -257,9 +265,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -267,9 +275,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -278,22 +286,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.7.1", + "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -313,9 +321,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf460bbff5f571bfc762da5102729f59f338be7db17a21fade44c5c4f5005350" +checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" dependencies = [ "proc-macro2", "quote", @@ -342,24 +350,24 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "ena" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" dependencies = [ "log", ] [[package]] name = "expect-test" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4661aca38d826eb7c72fe128e4238220616de4c0cc00db7bfc38e2e1364dd3" +checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" dependencies = [ "dissimilar", "once_cell", @@ -400,7 +408,6 @@ dependencies = [ "cargo_metadata", "command-group", "crossbeam-channel", - "jod-thread", "paths", "rustc-hash", "serde", @@ -419,12 +426,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -442,9 +443,9 @@ checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" [[package]] name = "gimli" -version = "0.27.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "hashbrown" @@ -497,6 +498,7 @@ dependencies = [ "smallvec", "stdx", "syntax", + "triomphe", "tt", ] @@ -507,7 +509,7 @@ dependencies = [ "anymap", "arrayvec", "base-db", - "bitflags", + "bitflags 2.1.0", "cfg", "cov-mark", "dashmap", @@ -533,6 +535,7 @@ dependencies = [ "syntax", "test-utils", "tracing", + "triomphe", "tt", ] @@ -557,6 +560,7 @@ dependencies = [ "stdx", "syntax", "tracing", + "triomphe", "tt", ] @@ -566,7 +570,7 @@ version = "0.0.0" dependencies = [ "arrayvec", "base-db", - "bitflags", + "bitflags 2.1.0", "chalk-derive", "chalk-ir", "chalk-recursive", @@ -582,6 +586,7 @@ dependencies = [ "itertools", "la-arena", "limit", + "nohash-hasher", "once_cell", "profile", "project-model", @@ -594,6 +599,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-tree", + "triomphe", "typed-arena", ] @@ -603,7 +609,7 @@ version = "0.0.20221221" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adabaadad9aa7576f97af02241cdf5554d62fb3d51a84cb05d77ba28edd3013f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "hkalbasi-rustc-ap-rustc_index", "tracing", ] @@ -644,6 +650,7 @@ dependencies = [ "ide-diagnostics", "ide-ssr", "itertools", + "nohash-hasher", "oorandom", "profile", "pulldown-cmark", @@ -655,6 +662,7 @@ dependencies = [ "text-edit", "toolchain", "tracing", + "triomphe", "url", ] @@ -710,7 +718,9 @@ dependencies = [ "indexmap", "itertools", "limit", + "line-index", "memchr", + "nohash-hasher", "once_cell", "oorandom", "parser", @@ -723,6 +733,7 @@ dependencies = [ "test-utils", "text-edit", "tracing", + "triomphe", "xshell", ] @@ -755,11 +766,13 @@ dependencies = [ "hir", "ide-db", "itertools", + "nohash-hasher", "parser", "stdx", "syntax", "test-utils", "text-edit", + "triomphe", ] [[package]] @@ -774,9 +787,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -788,7 +801,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "inotify-sys", "libc", ] @@ -819,6 +832,7 @@ dependencies = [ "hashbrown", "once_cell", "rustc-hash", + "triomphe", ] [[package]] @@ -832,9 +846,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jod-thread" @@ -858,7 +872,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] @@ -874,9 +888,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libloading" @@ -890,9 +904,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174" +checksum = "43a558e3d911bc3c7bfc8c78bc580b404d6e51c1cefbf656e176a94b49b0df40" dependencies = [ "cc", "libc", @@ -902,6 +916,14 @@ dependencies = [ name = "limit" version = "0.0.0" +[[package]] +name = "line-index" +version = "0.1.0-pre.1" +dependencies = [ + "nohash-hasher", + "text-size", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -938,7 +960,7 @@ version = "0.94.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" dependencies = [ - "bitflags", + "bitflags 1.3.2", "serde", "serde_json", "serde_repr", @@ -977,36 +999,27 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] [[package]] name = "mimalloc" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1" +checksum = "3d88dad3f985ec267a3fcb7a1726f5cb1a7e8cad8b646e70a84f967210df23da" dependencies = [ "libmimalloc-sys", ] @@ -1047,19 +1060,25 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", ] [[package]] -name = "notify" -version = "5.0.0" +name = "nohash-hasher" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "notify" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -1068,7 +1087,7 @@ dependencies = [ "libc", "mio", "walkdir", - "winapi", + "windows-sys", ] [[package]] @@ -1093,18 +1112,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.2" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c786513eb403643f2a88c244c2aaa270ef2153f55094587d0c48a3cf22a83" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" @@ -1173,16 +1192,16 @@ dependencies = [ "drop_bomb", "expect-test", "limit", - "rustc-ap-rustc_lexer", + "ra-ap-rustc_lexer", "sourcegen", "stdx", ] [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "paths" @@ -1242,6 +1261,7 @@ dependencies = [ "snap", "stdx", "tracing", + "triomphe", "tt", ] @@ -1257,6 +1277,7 @@ dependencies = [ "paths", "proc-macro-api", "proc-macro-test", + "stdx", "tt", ] @@ -1264,6 +1285,7 @@ dependencies = [ name = "proc-macro-srv-cli" version = "0.0.0" dependencies = [ + "proc-macro-api", "proc-macro-srv", ] @@ -1282,9 +1304,9 @@ version = "0.0.0" [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1312,6 +1334,7 @@ dependencies = [ "cargo_metadata", "cfg", "expect-test", + "itertools", "la-arena", "paths", "profile", @@ -1322,6 +1345,7 @@ dependencies = [ "stdx", "toolchain", "tracing", + "triomphe", ] [[package]] @@ -1350,7 +1374,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" dependencies = [ - "bitflags", + "bitflags 1.3.2", "memchr", "unicase", ] @@ -1366,18 +1390,28 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] -name = "rayon" -version = "1.6.1" +name = "ra-ap-rustc_lexer" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "e1c145702ed3f237918e512685185dc8a4d0edc3a5326c63d20361d8ba9b45b3" +dependencies = [ + "unic-emoji-char", + "unicode-xid", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -1385,9 +1419,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1401,14 +1435,14 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "regex-syntax", ] @@ -1424,19 +1458,19 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rowan" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5811547e7ba31e903fe48c8ceab10d40d70a101f3d15523c847cce91aa71f332" +checksum = "64449cfef9483a475ed56ae30e2da5ee96448789fb2aa240a04beb6a055078bf" dependencies = [ "countme", "hashbrown", - "memoffset 0.6.5", + "memoffset", "rustc-hash", "text-size", ] @@ -1451,6 +1485,7 @@ dependencies = [ "crossbeam-channel", "dissimilar", "expect-test", + "filetime", "flycheck", "hir", "hir-def", @@ -1459,16 +1494,18 @@ dependencies = [ "ide-db", "ide-ssr", "itertools", - "jod-thread", "lsp-server", "lsp-types", "mbe", "mimalloc", + "mio", + "nohash-hasher", "num_cpus", "oorandom", "parking_lot 0.12.1", + "parking_lot_core 0.9.6", "proc-macro-api", - "proc-macro-srv", + "proc-macro-srv-cli", "profile", "project-model", "rayon", @@ -1476,17 +1513,19 @@ dependencies = [ "scip", "serde", "serde_json", + "serde_repr", "sourcegen", "stdx", "syntax", "test-utils", - "threadpool", + "thiserror", "tikv-jemallocator", "toolchain", "tracing", "tracing-log", "tracing-subscriber", "tracing-tree", + "triomphe", "tt", "vfs", "vfs-notify", @@ -1495,20 +1534,11 @@ dependencies = [ "xshell", ] -[[package]] -name = "rustc-ap-rustc_lexer" -version = "727.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f40f26e7abdcd3b982f36c09a634cc6187988fbf6ec466c91f8d30a12ac0237" -dependencies = [ - "unicode-xid", -] - [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" [[package]] name = "rustc-hash" @@ -1518,9 +1548,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "salsa" @@ -1583,27 +1613,27 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.152" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" dependencies = [ "proc-macro2", "quote", @@ -1612,9 +1642,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "indexmap", "itoa", @@ -1624,9 +1654,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc" dependencies = [ "proc-macro2", "quote", @@ -1650,9 +1680,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smol_str" -version = "0.1.23" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" dependencies = [ "serde", ] @@ -1682,6 +1712,8 @@ version = "0.0.0" dependencies = [ "always-assert", "backtrace", + "crossbeam-channel", + "jod-thread", "libc", "miow", "winapi", @@ -1689,9 +1721,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -1724,15 +1756,16 @@ dependencies = [ "proc-macro2", "profile", "quote", + "ra-ap-rustc_lexer", "rayon", "rowan", - "rustc-ap-rustc_lexer", "rustc-hash", "smol_str", "sourcegen", "stdx", "test-utils", "text-edit", + "triomphe", "ungrammar", ] @@ -1763,18 +1796,18 @@ checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -1783,22 +1816,14 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "tikv-jemalloc-ctl" version = "0.5.0" @@ -1812,12 +1837,11 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.5.2+5.3.0-patched" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" dependencies = [ "cc", - "fs_extra", "libc", ] @@ -1833,9 +1857,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "serde", "time-core", @@ -1858,9 +1882,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toolchain" @@ -1942,6 +1966,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "triomphe" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db" + [[package]] name = "tt" version = "0.0.0" @@ -1962,6 +1992,47 @@ version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f" +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-emoji-char" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.6.0" @@ -1973,15 +2044,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -1994,9 +2065,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-xid" @@ -2034,6 +2105,7 @@ version = "0.0.0" dependencies = [ "fst", "indexmap", + "nohash-hasher", "paths", "rustc-hash", "stdx", @@ -2044,9 +2116,9 @@ name = "vfs-notify" version = "0.0.0" dependencies = [ "crossbeam-channel", - "jod-thread", "notify", "paths", + "stdx", "tracing", "vfs", "walkdir", @@ -2054,12 +2126,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -2117,45 +2188,45 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "write-json" diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 333f03ce2ffe..3050cf764a4c 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = ["xtask/", "lib/*", "crates/*"] exclude = ["crates/proc-macro-test/imp"] +resolver = "2" [workspace.package] rust-version = "1.66" @@ -74,5 +75,20 @@ toolchain = { path = "./crates/toolchain", version = "0.0.0" } tt = { path = "./crates/tt", version = "0.0.0" } vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } +line-index = { version = "0.1.0-pre.1", path = "./lib/line-index" } + # non-local crates -smallvec = { version = "1.10.0", features = ["const_new", "union", "const_generics"] } +smallvec = { version = "1.10.0", features = [ + "const_new", + "union", + "const_generics", +] } +smol_str = "0.2.0" +nohash-hasher = "0.2.0" +text-size = "1.1.0" +# the following crates are pinned to prevent us from pulling in syn 2 until all our dependencies have moved +serde = { version = "=1.0.156", features = ["derive"] } +serde_json = "1.0.94" +triomphe = { version = "0.1.8", default-features = false, features = ["std"] } + +rustc_lexer = { version = "0.1.0", package = "ra-ap-rustc_lexer" } diff --git a/src/tools/rust-analyzer/bench_data/glorious_old_parser b/src/tools/rust-analyzer/bench_data/glorious_old_parser index 764893daa12a..f593f2b2955a 100644 Binary files a/src/tools/rust-analyzer/bench_data/glorious_old_parser and b/src/tools/rust-analyzer/bench_data/glorious_old_parser differ diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml index f6a1075c190a..6001772c86ee 100644 --- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml @@ -15,6 +15,10 @@ doctest = false salsa = "0.17.0-pre.2" rustc-hash = "1.1.0" +triomphe.workspace = true + +la-arena = { version = "0.3.0", path = "../../lib/la-arena" } + # local deps cfg.workspace = true profile.workspace = true diff --git a/src/tools/rust-analyzer/crates/base-db/src/change.rs b/src/tools/rust-analyzer/crates/base-db/src/change.rs index b57f2345767d..6a3b36b23128 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/change.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/change.rs @@ -1,19 +1,21 @@ //! Defines a unit of change that can applied to the database to get the next //! state. Changes are transactional. -use std::{fmt, sync::Arc}; +use std::fmt; use salsa::Durability; +use triomphe::Arc; use vfs::FileId; -use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId}; +use crate::{CrateGraph, ProcMacros, SourceDatabaseExt, SourceRoot, SourceRootId}; /// Encapsulate a bunch of raw `.set` calls on the database. #[derive(Default)] pub struct Change { pub roots: Option>, - pub files_changed: Vec<(FileId, Option>)>, + pub files_changed: Vec<(FileId, Option>)>, pub crate_graph: Option, + pub proc_macros: Option, } impl fmt::Debug for Change { @@ -33,7 +35,7 @@ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { } impl Change { - pub fn new() -> Change { + pub fn new() -> Self { Change::default() } @@ -41,7 +43,7 @@ pub fn set_roots(&mut self, roots: Vec) { self.roots = Some(roots); } - pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { + pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { self.files_changed.push((file_id, new_text)) } @@ -49,6 +51,10 @@ pub fn set_crate_graph(&mut self, graph: CrateGraph) { self.crate_graph = Some(graph); } + pub fn set_proc_macros(&mut self, proc_macros: ProcMacros) { + self.proc_macros = Some(proc_macros); + } + pub fn apply(self, db: &mut dyn SourceDatabaseExt) { let _p = profile::span("RootDatabase::apply_change"); if let Some(roots) = self.roots { @@ -67,11 +73,14 @@ pub fn apply(self, db: &mut dyn SourceDatabaseExt) { let source_root = db.source_root(source_root_id); let durability = durability(&source_root); // XXX: can't actually remove the file, just reset the text - let text = text.unwrap_or_default(); + let text = text.unwrap_or_else(|| Arc::from("")); db.set_file_text_with_durability(file_id, text, durability) } if let Some(crate_graph) = self.crate_graph { - db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) + db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH); + } + if let Some(proc_macros) = self.proc_macros { + db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH); } } } diff --git a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs index 8a7e9dfadfed..5b11343173b3 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs @@ -1,24 +1,27 @@ //! A set of high-level utility fixture methods to use in tests. -use std::{mem, str::FromStr, sync::Arc}; +use std::{mem, str::FromStr, sync}; use cfg::CfgOptions; use rustc_hash::FxHashMap; use test_utils::{ - extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER, + extract_range_or_offset, Fixture, FixtureWithProjectMeta, RangeOrOffset, CURSOR_MARKER, + ESCAPED_CURSOR_MARKER, }; +use triomphe::Arc; use tt::token_id::{Leaf, Subtree, TokenTree}; use vfs::{file_set::FileSet, VfsPath}; use crate::{ input::{CrateName, CrateOrigin, LangCrateOrigin}, Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition, - FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, SourceDatabaseExt, - SourceRoot, SourceRootId, + FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel, + SourceDatabaseExt, SourceRoot, SourceRootId, }; pub const WORKSPACE: SourceRootId = SourceRootId(0); pub trait WithFixture: Default + SourceDatabaseExt + 'static { + #[track_caller] fn with_single_file(ra_fixture: &str) -> (Self, FileId) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -27,6 +30,7 @@ fn with_single_file(ra_fixture: &str) -> (Self, FileId) { (db, fixture.files[0]) } + #[track_caller] fn with_many_files(ra_fixture: &str) -> (Self, Vec) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -35,6 +39,7 @@ fn with_many_files(ra_fixture: &str) -> (Self, Vec) { (db, fixture.files) } + #[track_caller] fn with_files(ra_fixture: &str) -> Self { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -43,6 +48,7 @@ fn with_files(ra_fixture: &str) -> Self { db } + #[track_caller] fn with_files_extra_proc_macros( ra_fixture: &str, proc_macros: Vec<(String, ProcMacro)>, @@ -54,18 +60,21 @@ fn with_files_extra_proc_macros( db } + #[track_caller] fn with_position(ra_fixture: &str) -> (Self, FilePosition) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let offset = range_or_offset.expect_offset(); (db, FilePosition { file_id, offset }) } + #[track_caller] fn with_range(ra_fixture: &str) -> (Self, FileRange) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let range = range_or_offset.expect_range(); (db, FileRange { file_id, range }) } + #[track_caller] fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -100,9 +109,16 @@ pub fn parse(ra_fixture: &str) -> ChangeFixture { pub fn parse_with_proc_macros( ra_fixture: &str, - mut proc_macros: Vec<(String, ProcMacro)>, + mut proc_macro_defs: Vec<(String, ProcMacro)>, ) -> ChangeFixture { - let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture); + let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } = + FixtureWithProjectMeta::parse(ra_fixture); + let toolchain = toolchain + .map(|it| { + ReleaseChannel::from_str(&it) + .unwrap_or_else(|| panic!("unknown release channel found: {it}")) + }) + .unwrap_or(ReleaseChannel::Stable); let mut change = Change::new(); let mut files = Vec::new(); @@ -157,16 +173,16 @@ pub fn parse_with_proc_macros( meta.edition, Some(crate_name.clone().into()), version, - meta.cfg.clone(), meta.cfg, + Default::default(), meta.env, - Ok(Vec::new()), false, origin, meta.target_data_layout .as_deref() .map(Arc::from) .ok_or_else(|| "target_data_layout unset".into()), + Some(toolchain), ); let prev = crates.insert(crate_name.clone(), crate_id); assert!(prev.is_none()); @@ -182,7 +198,7 @@ pub fn parse_with_proc_macros( default_target_data_layout = meta.target_data_layout; } - change.change_file(file_id, Some(Arc::new(text))); + change.change_file(file_id, Some(Arc::from(text))); let path = VfsPath::new_virtual_path(meta.path); file_set.insert(file_id, path); files.push(file_id); @@ -197,15 +213,15 @@ pub fn parse_with_proc_macros( Edition::CURRENT, Some(CrateName::new("test").unwrap().into()), None, - default_cfg.clone(), default_cfg, + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, default_target_data_layout .map(|x| x.into()) .ok_or_else(|| "target_data_layout unset".into()), + Some(toolchain), ); } else { for (from, to, prelude) in crate_deps { @@ -232,7 +248,7 @@ pub fn parse_with_proc_macros( fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string())); roots.push(SourceRoot::new_library(fs)); - change.change_file(core_file, Some(Arc::new(mini_core.source_code()))); + change.change_file(core_file, Some(Arc::from(mini_core.source_code()))); let all_crates = crate_graph.crates_in_topological_order(); @@ -241,13 +257,13 @@ pub fn parse_with_proc_macros( Edition::Edition2021, Some(CrateDisplayName::from_canonical_name("core".to_string())), None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, CrateOrigin::Lang(LangCrateOrigin::Core), target_layout.clone(), + Some(toolchain), ); for krate in all_crates { @@ -257,12 +273,13 @@ pub fn parse_with_proc_macros( } } + let mut proc_macros = ProcMacros::default(); if !proc_macro_names.is_empty() { let proc_lib_file = file_id; file_id.0 += 1; - proc_macros.extend(default_test_proc_macros()); - let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macros); + proc_macro_defs.extend(default_test_proc_macros()); + let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs); let mut fs = FileSet::default(); fs.insert( proc_lib_file, @@ -270,7 +287,7 @@ pub fn parse_with_proc_macros( ); roots.push(SourceRoot::new_library(fs)); - change.change_file(proc_lib_file, Some(Arc::new(source))); + change.change_file(proc_lib_file, Some(Arc::from(source))); let all_crates = crate_graph.crates_in_topological_order(); @@ -279,14 +296,15 @@ pub fn parse_with_proc_macros( Edition::Edition2021, Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())), None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(proc_macro), true, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, target_layout, + Some(toolchain), ); + proc_macros.insert(proc_macros_crate, Ok(proc_macro)); for krate in all_crates { crate_graph @@ -305,6 +323,7 @@ pub fn parse_with_proc_macros( roots.push(root); change.set_roots(roots); change.set_crate_graph(crate_graph); + change.set_proc_macros(proc_macros); ChangeFixture { file_position, files, change } } @@ -323,7 +342,7 @@ pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream { ProcMacro { name: "identity".into(), kind: crate::ProcMacroKind::Attr, - expander: Arc::new(IdentityProcMacroExpander), + expander: sync::Arc::new(IdentityProcMacroExpander), }, ), ( @@ -337,7 +356,7 @@ pub fn derive_identity(item: TokenStream) -> TokenStream { ProcMacro { name: "DeriveIdentity".into(), kind: crate::ProcMacroKind::CustomDerive, - expander: Arc::new(IdentityProcMacroExpander), + expander: sync::Arc::new(IdentityProcMacroExpander), }, ), ( @@ -351,7 +370,7 @@ pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream { ProcMacro { name: "input_replace".into(), kind: crate::ProcMacroKind::Attr, - expander: Arc::new(AttributeInputReplaceProcMacroExpander), + expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander), }, ), ( @@ -365,7 +384,7 @@ pub fn mirror(input: TokenStream) -> TokenStream { ProcMacro { name: "mirror".into(), kind: crate::ProcMacroKind::FuncLike, - expander: Arc::new(MirrorProcMacroExpander), + expander: sync::Arc::new(MirrorProcMacroExpander), }, ), ( @@ -379,7 +398,7 @@ pub fn shorten(input: TokenStream) -> TokenStream { ProcMacro { name: "shorten".into(), kind: crate::ProcMacroKind::FuncLike, - expander: Arc::new(ShortenProcMacroExpander), + expander: sync::Arc::new(ShortenProcMacroExpander), }, ), ] @@ -428,7 +447,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()), name: None }) + (version, CrateOrigin::Local { repo: Some(url.to_owned()), name: None }) } _ => panic!("Bad crates.io parameter: {data}"), }, @@ -436,10 +455,9 @@ fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option) { }; (a.to_owned(), origin, Some(version.to_string())) } else { - let crate_origin = match &*crate_str { - "std" => CrateOrigin::Lang(LangCrateOrigin::Std), - "core" => CrateOrigin::Lang(LangCrateOrigin::Core), - _ => CrateOrigin::CratesIo { repo: None, name: None }, + let crate_origin = match LangCrateOrigin::from(&*crate_str) { + LangCrateOrigin::Other => CrateOrigin::Local { repo: None, name: None }, + origin => CrateOrigin::Lang(origin), }; (crate_str, crate_origin, None) } diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 43388e915b5d..e8d521b42f86 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -6,14 +6,20 @@ //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how //! actual IO is done and lowered to input. -use std::{fmt, ops, panic::RefUnwindSafe, str::FromStr, sync::Arc}; +use std::{fmt, mem, ops, panic::RefUnwindSafe, str::FromStr, sync}; use cfg::CfgOptions; -use rustc_hash::FxHashMap; -use stdx::hash::{NoHashHashMap, NoHashHashSet}; +use la_arena::{Arena, Idx}; +use rustc_hash::{FxHashMap, FxHashSet}; use syntax::SmolStr; +use triomphe::Arc; use tt::token_id::Subtree; -use vfs::{file_set::FileSet, AnchoredPath, FileId, VfsPath}; +use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; + +// Map from crate id to the name of the crate and path of the proc-macro. If the value is `None`, +// then the crate for the proc-macro hasn't been build yet as the build data is missing. +pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; +pub type ProcMacros = FxHashMap; /// Files are grouped into source roots. A source root is a directory on the /// file systems which is watched for changes. Typically it corresponds to a @@ -79,17 +85,22 @@ pub fn iter(&self) -> impl Iterator + '_ { /// /// `CrateGraph` is `!Serialize` by design, see /// -#[derive(Debug, Clone, Default /* Serialize, Deserialize */)] +#[derive(Clone, Default)] pub struct CrateGraph { - arena: NoHashHashMap, + arena: Arena, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CrateId(pub u32); +impl fmt::Debug for CrateGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.arena.iter().map(|(id, data)| (u32::from(id.into_raw()), data))) + .finish() + } +} -impl stdx::hash::NoHashHashable for CrateId {} +pub type CrateId = Idx; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CrateName(SmolStr); impl CrateName { @@ -130,8 +141,12 @@ fn deref(&self) -> &str { /// Origin of the crates. It is used in emitting monikers. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CrateOrigin { - /// Crates that are from crates.io official registry, - CratesIo { repo: Option, name: Option }, + /// Crates that are from the rustc workspace + Rustc { name: String }, + /// Crates that are workspace members, + Local { repo: Option, name: Option }, + /// Crates that are non member libraries. + Library { repo: Option, name: String }, /// Crates that are provided by the language, like std, core, proc-macro, ... Lang(LangCrateOrigin), } @@ -173,7 +188,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CrateDisplayName { // The name we use to display various paths (with `_`). crate_name: CrateName, @@ -249,10 +264,36 @@ pub enum ProcMacroExpansionError { pub struct ProcMacro { pub name: SmolStr, pub kind: ProcMacroKind, - pub expander: Arc, + pub expander: sync::Arc, } -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ReleaseChannel { + Stable, + Beta, + Nightly, +} + +impl ReleaseChannel { + pub fn as_str(self) -> &'static str { + match self { + ReleaseChannel::Stable => "stable", + ReleaseChannel::Beta => "beta", + ReleaseChannel::Nightly => "nightly", + } + } + + pub fn from_str(str: &str) -> Option { + Some(match str { + "" => ReleaseChannel::Stable, + "nightly" => ReleaseChannel::Nightly, + _ if str.starts_with("beta") => ReleaseChannel::Beta, + _ => return None, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CrateData { pub root_file_id: FileId, pub edition: Edition, @@ -265,13 +306,15 @@ pub struct CrateData { /// `Dependency` matters), this name should only be used for UI. pub display_name: Option, pub cfg_options: CfgOptions, - pub potential_cfg_options: CfgOptions, - pub target_layout: TargetLayoutLoadResult, + /// The cfg options that could be used by the crate + pub potential_cfg_options: Option, pub env: Env, pub dependencies: Vec, - pub proc_macro: ProcMacroLoadResult, pub origin: CrateOrigin, pub is_proc_macro: bool, + // FIXME: These things should not be per crate! These are more per workspace crate graph level things + pub target_layout: TargetLayoutLoadResult, + pub channel: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -290,7 +333,7 @@ pub struct Env { entries: FxHashMap, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Dependency { pub crate_id: CrateId, pub name: CrateName, @@ -320,12 +363,12 @@ pub fn add_crate_root( display_name: Option, version: Option, cfg_options: CfgOptions, - potential_cfg_options: CfgOptions, + potential_cfg_options: Option, env: Env, - proc_macro: ProcMacroLoadResult, is_proc_macro: bool, origin: CrateOrigin, target_layout: Result, Arc>, + channel: Option, ) -> CrateId { let data = CrateData { root_file_id, @@ -335,16 +378,44 @@ pub fn add_crate_root( cfg_options, potential_cfg_options, env, - proc_macro, dependencies: Vec::new(), origin, target_layout, is_proc_macro, + channel, }; - let crate_id = CrateId(self.arena.len() as u32); - let prev = self.arena.insert(crate_id, data); - assert!(prev.is_none()); - crate_id + self.arena.alloc(data) + } + + /// Remove the crate from crate graph. If any crates depend on this crate, the dependency would be replaced + /// with the second input. + pub fn remove_and_replace( + &mut self, + id: CrateId, + replace_with: CrateId, + ) -> Result<(), CyclicDependenciesError> { + for (x, data) in self.arena.iter() { + if x == id { + continue; + } + for edge in &data.dependencies { + if edge.crate_id == id { + self.check_cycle_after_dependency(edge.crate_id, replace_with)?; + } + } + } + // if everything was ok, start to replace + for (x, data) in self.arena.iter_mut() { + if x == id { + continue; + } + for edge in &mut data.dependencies { + if edge.crate_id == id { + edge.crate_id = replace_with; + } + } + } + Ok(()) } pub fn add_dep( @@ -354,17 +425,26 @@ pub fn add_dep( ) -> Result<(), CyclicDependenciesError> { let _p = profile::span("add_dep"); - // Check if adding a dep from `from` to `to` creates a cycle. To figure - // that out, look for a path in the *opposite* direction, from `to` to - // `from`. - if let Some(path) = self.find_path(&mut NoHashHashSet::default(), dep.crate_id, from) { + self.check_cycle_after_dependency(from, dep.crate_id)?; + + self.arena[from].add_dep(dep); + Ok(()) + } + + /// Check if adding a dep from `from` to `to` creates a cycle. To figure + /// that out, look for a path in the *opposite* direction, from `to` to + /// `from`. + fn check_cycle_after_dependency( + &self, + from: CrateId, + to: CrateId, + ) -> Result<(), CyclicDependenciesError> { + if let Some(path) = self.find_path(&mut FxHashSet::default(), to, from) { let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect(); let err = CyclicDependenciesError { path }; - assert!(err.from().0 == from && err.to().0 == dep.crate_id); + assert!(err.from().0 == from && err.to().0 == to); return Err(err); } - - self.arena.get_mut(&from).unwrap().add_dep(dep); Ok(()) } @@ -373,14 +453,14 @@ pub fn is_empty(&self) -> bool { } pub fn iter(&self) -> impl Iterator + '_ { - self.arena.keys().copied() + self.arena.iter().map(|(idx, _)| idx) } /// Returns an iterator over all transitive dependencies of the given crate, /// including the crate itself. pub fn transitive_deps(&self, of: CrateId) -> impl Iterator { let mut worklist = vec![of]; - let mut deps = NoHashHashSet::default(); + let mut deps = FxHashSet::default(); while let Some(krate) = worklist.pop() { if !deps.insert(krate) { @@ -397,11 +477,11 @@ pub fn transitive_deps(&self, of: CrateId) -> impl Iterator { /// including the crate itself. pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator { let mut worklist = vec![of]; - let mut rev_deps = NoHashHashSet::default(); + let mut rev_deps = FxHashSet::default(); rev_deps.insert(of); - let mut inverted_graph = NoHashHashMap::<_, Vec<_>>::default(); - self.arena.iter().for_each(|(&krate, data)| { + let mut inverted_graph = FxHashMap::<_, Vec<_>>::default(); + self.arena.iter().for_each(|(krate, data)| { data.dependencies .iter() .for_each(|dep| inverted_graph.entry(dep.crate_id).or_default().push(krate)) @@ -424,9 +504,9 @@ pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator /// come before the crate itself). pub fn crates_in_topological_order(&self) -> Vec { let mut res = Vec::new(); - let mut visited = NoHashHashSet::default(); + let mut visited = FxHashSet::default(); - for krate in self.arena.keys().copied() { + for krate in self.iter() { go(self, &mut visited, &mut res, krate); } @@ -434,7 +514,7 @@ pub fn crates_in_topological_order(&self) -> Vec { fn go( graph: &CrateGraph, - visited: &mut NoHashHashSet, + visited: &mut FxHashSet, res: &mut Vec, source: CrateId, ) { @@ -450,31 +530,56 @@ fn go( // FIXME: this only finds one crate with the given root; we could have multiple pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option { - let (&crate_id, _) = + let (crate_id, _) = self.arena.iter().find(|(_crate_id, data)| data.root_file_id == file_id)?; Some(crate_id) } + pub fn sort_deps(&mut self) { + self.arena + .iter_mut() + .for_each(|(_, data)| data.dependencies.sort_by_key(|dep| dep.crate_id)); + } + /// Extends this crate graph by adding a complete disjoint second crate - /// graph. + /// graph and adjust the ids in the [`ProcMacroPaths`] accordingly. /// - /// The ids of the crates in the `other` graph are shifted by the return - /// amount. - pub fn extend(&mut self, other: CrateGraph) -> u32 { - let start = self.arena.len() as u32; - self.arena.extend(other.arena.into_iter().map(|(id, mut data)| { - let new_id = id.shift(start); - for dep in &mut data.dependencies { - dep.crate_id = dep.crate_id.shift(start); + /// This will deduplicate the crates of the graph where possible. + /// Note that for deduplication to fully work, `self`'s crate dependencies must be sorted by crate id. + /// If the crate dependencies were sorted, the resulting graph from this `extend` call will also have the crate dependencies sorted. + pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) { + let topo = other.crates_in_topological_order(); + let mut id_map: FxHashMap = FxHashMap::default(); + + for topo in topo { + let crate_data = &mut other.arena[topo]; + crate_data.dependencies.iter_mut().for_each(|dep| dep.crate_id = id_map[&dep.crate_id]); + crate_data.dependencies.sort_by_key(|dep| dep.crate_id); + + let res = self.arena.iter().find_map( + |(id, data)| { + if data == crate_data { + Some(id) + } else { + None + } + }, + ); + if let Some(res) = res { + id_map.insert(topo, res); + } else { + let id = self.arena.alloc(crate_data.clone()); + id_map.insert(topo, id); } - (new_id, data) - })); - start + } + + *proc_macros = + mem::take(proc_macros).into_iter().map(|(id, macros)| (id_map[&id], macros)).collect(); } fn find_path( &self, - visited: &mut NoHashHashSet, + visited: &mut FxHashSet, from: CrateId, to: CrateId, ) -> Option> { @@ -500,14 +605,14 @@ fn find_path( // Work around for https://github.com/rust-lang/rust-analyzer/issues/6038. // As hacky as it gets. pub fn patch_cfg_if(&mut self) -> bool { - let cfg_if = self.hacky_find_crate("cfg_if"); - let std = self.hacky_find_crate("std"); + // we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works + let cfg_if = + self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone()); + let std = self.hacky_find_crate("std").next(); match (cfg_if, std) { (Some(cfg_if), Some(std)) => { - self.arena.get_mut(&cfg_if).unwrap().dependencies.clear(); - self.arena - .get_mut(&std) - .unwrap() + self.arena[cfg_if].dependencies.clear(); + self.arena[std] .dependencies .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if)); true @@ -516,21 +621,15 @@ pub fn patch_cfg_if(&mut self) -> bool { } } - fn hacky_find_crate(&self, display_name: &str) -> Option { - self.iter().find(|it| self[*it].display_name.as_deref() == Some(display_name)) + fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator + 'a { + self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name)) } } impl ops::Index for CrateGraph { type Output = CrateData; fn index(&self, crate_id: CrateId) -> &CrateData { - &self.arena[&crate_id] - } -} - -impl CrateId { - fn shift(self, amount: u32) -> CrateId { - CrateId(self.0 + amount) + &self.arena[crate_id] } } @@ -632,7 +731,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { mod tests { use crate::CrateOrigin; - use super::{CfgOptions, CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId}; + use super::{CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId}; #[test] fn detect_cyclic_dependency_indirect() { @@ -642,39 +741,39 @@ fn detect_cyclic_dependency_indirect() { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate3 = graph.add_crate_root( FileId(3u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -695,26 +794,26 @@ fn detect_cyclic_dependency_direct() { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -732,39 +831,39 @@ fn it_works() { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate3 = graph.add_crate_root( FileId(3u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -782,26 +881,26 @@ fn dashes_are_normalized() { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep( diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 9720db9d8ace..af204e44e6ee 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -6,18 +6,19 @@ mod change; pub mod fixture; -use std::{panic, sync::Arc}; +use std::panic; -use stdx::hash::NoHashHashSet; +use rustc_hash::FxHashSet; use syntax::{ast, Parse, SourceFile, TextRange, TextSize}; +use triomphe::Arc; pub use crate::{ change::Change, input::{ CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env, LangCrateOrigin, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, - ProcMacroId, ProcMacroKind, ProcMacroLoadResult, SourceRoot, SourceRootId, - TargetLayoutLoadResult, + ProcMacroId, ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, ProcMacros, + ReleaseChannel, SourceRoot, SourceRootId, TargetLayoutLoadResult, }, }; pub use salsa::{self, Cancelled}; @@ -53,13 +54,13 @@ pub struct FileRange { pub range: TextRange, } -pub const DEFAULT_LRU_CAP: usize = 128; +pub const DEFAULT_PARSE_LRU_CAP: usize = 128; pub trait FileLoader { /// Text of the file. - fn file_text(&self, file_id: FileId) -> Arc; + fn file_text(&self, file_id: FileId) -> Arc; fn resolve_path(&self, path: AnchoredPath<'_>) -> Option; - fn relevant_crates(&self, file_id: FileId) -> Arc>; + fn relevant_crates(&self, file_id: FileId) -> Arc>; } /// Database which stores all significant input facts: source code and project @@ -73,6 +74,10 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug { /// The crate graph. #[salsa::input] fn crate_graph(&self) -> Arc; + + /// The crate graph. + #[salsa::input] + fn proc_macros(&self) -> Arc; } fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse { @@ -86,7 +91,7 @@ fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse Arc; + fn file_text(&self, file_id: FileId) -> Arc; /// Path to a file, relative to the root of its source root. /// Source root of the file. #[salsa::input] @@ -95,10 +100,10 @@ pub trait SourceDatabaseExt: SourceDatabase { #[salsa::input] fn source_root(&self, id: SourceRootId) -> Arc; - fn source_root_crates(&self, id: SourceRootId) -> Arc>; + fn source_root_crates(&self, id: SourceRootId) -> Arc>; } -fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc> { +fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc> { let graph = db.crate_graph(); let res = graph .iter() @@ -114,7 +119,7 @@ fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc(pub T); impl FileLoader for FileLoaderDelegate<&'_ T> { - fn file_text(&self, file_id: FileId) -> Arc { + fn file_text(&self, file_id: FileId) -> Arc { SourceDatabaseExt::file_text(self.0, file_id) } fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { @@ -124,7 +129,7 @@ fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { source_root.resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { let _p = profile::span("relevant_crates"); let source_root = self.0.file_source_root(file_id); self.0.source_root_crates(source_root) diff --git a/src/tools/rust-analyzer/crates/cfg/src/lib.rs b/src/tools/rust-analyzer/crates/cfg/src/lib.rs index 30709c968dac..495119d5519c 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/lib.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/lib.rs @@ -86,7 +86,7 @@ pub fn get_cfg_values<'a>( } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CfgDiff { // Invariants: No duplicates, no atom that's both in `enable` and `disable`. enable: Vec, diff --git a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml index 609d18c4eea3..3f6671b1c43d 100644 --- a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml +++ b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml @@ -16,9 +16,8 @@ crossbeam-channel = "0.5.5" 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.86" -jod-thread = "0.1.2" +serde_json.workspace = true +serde.workspace = true command-group = "2.0.1" # local deps diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs index accb14a51deb..fbb943ccb99d 100644 --- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs +++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs @@ -77,7 +77,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub struct FlycheckHandle { // XXX: drop order is significant sender: Sender, - _thread: jod_thread::JoinHandle, + _thread: stdx::thread::JoinHandle, id: usize, } @@ -90,7 +90,7 @@ pub fn spawn( ) -> FlycheckHandle { let actor = FlycheckActor::new(id, sender, config, workspace_root); let (sender, receiver) = unbounded::(); - let thread = jod_thread::Builder::new() + let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) .name("Flycheck".to_owned()) .spawn(move || actor.run(receiver)) .expect("failed to spawn thread"); @@ -395,7 +395,7 @@ struct CargoHandle { /// The handle to the actual cargo process. As we cannot cancel directly from with /// a read syscall dropping and therefore terminating the process is our best option. child: JodGroupChild, - thread: jod_thread::JoinHandle>, + thread: stdx::thread::JoinHandle>, receiver: Receiver, } @@ -409,7 +409,7 @@ fn spawn(mut command: Command) -> std::io::Result { let (sender, receiver) = unbounded(); let actor = CargoActor::new(sender, stdout, stderr); - let thread = jod_thread::Builder::new() + let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) .name("CargoHandle".to_owned()) .spawn(move || actor.run()) .expect("failed to spawn thread"); @@ -485,7 +485,7 @@ fn run(self) -> io::Result<(bool, String)> { error.push_str(line); error.push('\n'); - return false; + false }; let output = streaming_output( self.stdout, diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml index 31d4018d2b6a..83c7051646ed 100644 --- a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml @@ -14,7 +14,7 @@ doctest = false [dependencies] anymap = "1.0.0-beta.2" arrayvec = "0.7.2" -bitflags = "1.3.2" +bitflags = "2.1.0" 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.4.0", features = ["raw-api"] } @@ -29,6 +29,7 @@ once_cell = "1.17.0" rustc-hash = "1.1.0" smallvec.workspace = true tracing = "0.1.35" +triomphe.workspace = true rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs index 200072c172eb..bab3bbc2329a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs @@ -1,6 +1,11 @@ //! A higher level attributes based on TokenTree, with also some shortcuts. -use std::{hash::Hash, ops, sync::Arc}; +pub mod builtin; + +#[cfg(test)] +mod tests; + +use std::{hash::Hash, ops}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; @@ -16,14 +21,16 @@ ast::{self, HasAttrs, IsString}, AstPtr, AstToken, SmolStr, TextRange, TextSize, }; +use triomphe::Arc; use crate::{ db::DefDatabase, item_tree::{AttrOwner, Fields, ItemTreeId, ItemTreeNode}, + lang_item::LangItem, nameres::{ModuleOrigin, ModuleSource}, src::{HasChildSource, HasSource}, - AdtId, AttrDefId, EnumId, GenericParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroId, - VariantId, + AdtId, AssocItemLoc, AttrDefId, EnumId, GenericParamId, ItemLoc, LocalEnumVariantId, + LocalFieldId, Lookup, MacroId, VariantId, }; /// Holds documentation @@ -88,6 +95,7 @@ pub(crate) fn variants_attrs_query( db: &dyn DefDatabase, e: EnumId, ) -> Arc> { + let _p = profile::span("variants_attrs_query"); // FIXME: There should be some proper form of mapping between item tree enum variant ids and hir enum variant ids let mut res = ArenaMap::default(); @@ -114,6 +122,7 @@ pub(crate) fn fields_attrs_query( db: &dyn DefDatabase, v: VariantId, ) -> Arc> { + let _p = profile::span("fields_attrs_query"); // FIXME: There should be some proper form of mapping between item tree field ids and hir field ids let mut res = ArenaMap::default(); @@ -175,13 +184,13 @@ pub(crate) fn fields_attrs_query( Arc::new(res) } - - pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { - AttrQuery { attrs: self, key } - } } impl Attrs { + pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { + AttrQuery { attrs: self, key } + } + pub fn cfg(&self) -> Option { let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse); let first = cfgs.next()?; @@ -193,6 +202,7 @@ pub fn cfg(&self) -> Option { None => Some(first), } } + pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { match self.cfg() { None => true, @@ -204,6 +214,10 @@ pub fn lang(&self) -> Option<&SmolStr> { self.by_key("lang").string_value() } + pub fn lang_item(&self) -> Option { + self.by_key("lang").string_value().and_then(|it| LangItem::from_str(it)) + } + pub fn docs(&self) -> Option { let docs = self.by_key("doc").attrs().filter_map(|attr| attr.string_value()); let indent = doc_indent(self); @@ -238,6 +252,14 @@ pub fn has_doc_hidden(&self) -> bool { }) } + pub fn doc_exprs(&self) -> impl Iterator + '_ { + self.by_key("doc").tt_values().map(DocExpr::parse) + } + + pub fn doc_aliases(&self) -> impl Iterator + '_ { + self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec()) + } + pub fn is_proc_macro(&self) -> bool { self.by_key("proc_macro").exists() } @@ -249,10 +271,120 @@ pub fn is_proc_macro_attribute(&self) -> bool { pub fn is_proc_macro_derive(&self) -> bool { self.by_key("proc_macro_derive").exists() } + + pub fn is_unstable(&self) -> bool { + self.by_key("unstable").exists() + } +} + +use std::slice::Iter as SliceIter; +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum DocAtom { + /// eg. `#[doc(hidden)]` + Flag(SmolStr), + /// eg. `#[doc(alias = "x")]` + /// + /// Note that a key can have multiple values that are all considered "active" at the same time. + /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`. + KeyValue { key: SmolStr, value: SmolStr }, +} + +// Adapted from `CfgExpr` parsing code +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))] +pub enum DocExpr { + Invalid, + /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]` + Atom(DocAtom), + /// eg. `#[doc(alias("x", "y"))]` + Alias(Vec), +} + +impl From for DocExpr { + fn from(atom: DocAtom) -> Self { + DocExpr::Atom(atom) + } +} + +impl DocExpr { + fn parse(tt: &tt::Subtree) -> DocExpr { + next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid) + } + + pub fn aliases(&self) -> &[SmolStr] { + match self { + DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => { + std::slice::from_ref(value) + } + DocExpr::Alias(aliases) => aliases, + _ => &[], + } + } +} + +fn next_doc_expr(it: &mut SliceIter<'_, tt::TokenTree>) -> Option { + let name = match it.next() { + None => return None, + Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(), + Some(_) => return Some(DocExpr::Invalid), + }; + + // Peek + let ret = match it.as_slice().first() { + Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => { + match it.as_slice().get(1) { + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => { + it.next(); + it.next(); + // FIXME: escape? raw string? + let value = + SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); + DocAtom::KeyValue { key: name, value }.into() + } + _ => return Some(DocExpr::Invalid), + } + } + Some(tt::TokenTree::Subtree(subtree)) => { + it.next(); + let subs = parse_comma_sep(subtree); + match name.as_str() { + "alias" => DocExpr::Alias(subs), + _ => DocExpr::Invalid, + } + } + _ => DocAtom::Flag(name).into(), + }; + + // Eat comma separator + if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() { + if punct.char == ',' { + it.next(); + } + } + Some(ret) +} + +fn parse_comma_sep(subtree: &tt::Subtree) -> Vec { + subtree + .token_trees + .iter() + .filter_map(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { + // FIXME: escape? raw string? + Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"'))) + } + _ => None, + }) + .collect() } impl AttrsWithOwner { - pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { + pub(crate) fn attrs_with_owner(db: &dyn DefDatabase, owner: AttrDefId) -> Self { + Self { attrs: db.attrs(owner), owner } + } + + pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs { + let _p = profile::span("attrs_query"); // FIXME: this should use `Trace` to avoid duplication in `source_map` below let raw_attrs = match def { AttrDefId::ModuleId(module) => { @@ -286,31 +418,29 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { } } AttrDefId::FieldId(it) => { - return Self { attrs: db.fields_attrs(it.parent)[it.local_id].clone(), owner: def }; + return db.fields_attrs(it.parent)[it.local_id].clone(); } AttrDefId::EnumVariantId(it) => { - return Self { - attrs: db.variants_attrs(it.parent)[it.local_id].clone(), - owner: def, - }; + return db.variants_attrs(it.parent)[it.local_id].clone(); } + // FIXME: DRY this up AttrDefId::AdtId(it) => match it { - AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AdtId::StructId(it) => attrs_from_item_tree_loc(db, it), + AdtId::EnumId(it) => attrs_from_item_tree_loc(db, it), + AdtId::UnionId(it) => attrs_from_item_tree_loc(db, it), }, - AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::TraitAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::TraitId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::TraitAliasId(it) => attrs_from_item_tree_loc(db, it), AttrDefId::MacroId(it) => match it { - MacroId::Macro2Id(it) => attrs_from_item_tree(it.lookup(db).id, db), - MacroId::MacroRulesId(it) => attrs_from_item_tree(it.lookup(db).id, db), - MacroId::ProcMacroId(it) => attrs_from_item_tree(it.lookup(db).id, db), + MacroId::Macro2Id(it) => attrs_from_item_tree(db, it.lookup(db).id), + MacroId::MacroRulesId(it) => attrs_from_item_tree(db, it.lookup(db).id), + MacroId::ProcMacroId(it) => attrs_from_item_tree(db, it.lookup(db).id), }, - AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::ImplId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::ConstId(it) => attrs_from_item_tree_assoc(db, it), + AttrDefId::StaticId(it) => attrs_from_item_tree_assoc(db, it), + AttrDefId::FunctionId(it) => attrs_from_item_tree_assoc(db, it), + AttrDefId::TypeAliasId(it) => attrs_from_item_tree_assoc(db, it), AttrDefId::GenericParamId(it) => match it { GenericParamId::ConstParamId(it) => { let src = it.parent().child_source(db); @@ -331,11 +461,11 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { RawAttrs::from_attrs_owner(db.upcast(), src.with_value(&src.value[it.local_id])) } }, - AttrDefId::ExternBlockId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it), }; let attrs = raw_attrs.filter(db.upcast(), def.krate(db)); - Self { attrs: Attrs(attrs), owner: def } + Attrs(attrs) } pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { @@ -371,7 +501,7 @@ pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { AttrDefId::FieldId(id) => { let map = db.fields_attrs_source_map(id.parent); let file_id = id.parent.file_id(db); - let root = db.parse_or_expand(file_id).unwrap(); + let root = db.parse_or_expand(file_id); let owner = match &map[id.local_id] { Either::Left(it) => ast::AnyHasAttrs::new(it.to_node(&root)), Either::Right(it) => ast::AnyHasAttrs::new(it.to_node(&root)), @@ -379,28 +509,28 @@ pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { InFile::new(file_id, owner) } AttrDefId::AdtId(adt) => match adt { - AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AdtId::StructId(id) => any_has_attrs(db, id), + AdtId::UnionId(id) => any_has_attrs(db, id), + AdtId::EnumId(id) => any_has_attrs(db, id), }, - AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::FunctionId(id) => any_has_attrs(db, id), AttrDefId::EnumVariantId(id) => { let map = db.variants_attrs_source_map(id.parent); let file_id = id.parent.lookup(db).id.file_id(); - let root = db.parse_or_expand(file_id).unwrap(); + let root = db.parse_or_expand(file_id); InFile::new(file_id, ast::AnyHasAttrs::new(map[id.local_id].to_node(&root))) } - AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::TraitAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::StaticId(id) => any_has_attrs(db, id), + AttrDefId::ConstId(id) => any_has_attrs(db, id), + AttrDefId::TraitId(id) => any_has_attrs(db, id), + AttrDefId::TraitAliasId(id) => any_has_attrs(db, id), + AttrDefId::TypeAliasId(id) => any_has_attrs(db, id), AttrDefId::MacroId(id) => match id { - MacroId::Macro2Id(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - MacroId::MacroRulesId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - MacroId::ProcMacroId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + MacroId::Macro2Id(id) => any_has_attrs(db, id), + MacroId::MacroRulesId(id) => any_has_attrs(db, id), + MacroId::ProcMacroId(id) => any_has_attrs(db, id), }, - AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::ImplId(id) => any_has_attrs(db, id), AttrDefId::GenericParamId(id) => match id { GenericParamId::ConstParamId(id) => id .parent() @@ -415,7 +545,7 @@ pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { .child_source(db) .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())), }, - AttrDefId::ExternBlockId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::ExternBlockId(id) => any_has_attrs(db, id), }; AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) @@ -635,19 +765,42 @@ pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> { .nth(2); match name { - Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ref text, ..}))) => Some(text), + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ ref text, ..}))) => Some(text), _ => None } }) } } -fn attrs_from_item_tree(id: ItemTreeId, db: &dyn DefDatabase) -> RawAttrs { +fn any_has_attrs( + db: &dyn DefDatabase, + id: impl Lookup>, +) -> InFile { + id.lookup(db).source(db).map(ast::AnyHasAttrs::new) +} + +fn attrs_from_item_tree(db: &dyn DefDatabase, id: ItemTreeId) -> RawAttrs { let tree = id.item_tree(db); let mod_item = N::id_to_mod_item(id.value); tree.raw_attrs(mod_item.into()).clone() } +fn attrs_from_item_tree_loc( + db: &dyn DefDatabase, + lookup: impl Lookup>, +) -> RawAttrs { + let id = lookup.lookup(db).id; + attrs_from_item_tree(db, id) +} + +fn attrs_from_item_tree_assoc( + db: &dyn DefDatabase, + lookup: impl Lookup>, +) -> RawAttrs { + let id = lookup.lookup(db).id; + attrs_from_item_tree(db, id) +} + pub(crate) fn variants_attrs_source_map( db: &dyn DefDatabase, def: EnumId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs similarity index 83% rename from src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs rename to src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs index 142b12290194..cead64a33749 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs @@ -2,7 +2,7 @@ //! //! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`. //! -//! It was last synchronized with upstream commit c1a2db3372a4d6896744919284f3287650a38ab7. +//! It was last synchronized with upstream commit e29821ff85a2a3000d226f99f62f89464028d5d6. //! //! The macros were adjusted to only expand to the attribute name, since that is all we need to do //! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to @@ -108,7 +108,7 @@ macro_rules! experimental { }; } -/// "Inert" built-in attributes that have a special meaning to rustc or rustdoc. +/// Attributes that have a special meaning to rustc or rustdoc. #[rustfmt::skip] pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== @@ -123,7 +123,7 @@ macro_rules! experimental { ungated!(ignore, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing), ungated!( should_panic, Normal, - template!(Word, List: r#"expected = "reason"#, NameValueStr: "reason"), FutureWarnFollowing, + template!(Word, List: r#"expected = "reason""#, NameValueStr: "reason"), FutureWarnFollowing, ), // FIXME(Centril): This can be used on stable but shouldn't. ungated!(reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"), ErrorFollowing), @@ -142,20 +142,24 @@ macro_rules! experimental { // Lints: ungated!( - warn, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + warn, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, ), ungated!( - allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, ), gated!( expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk, lint_reasons, experimental!(expect) ), ungated!( - forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, ), ungated!( - deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, ), ungated!(must_use, Normal, template!(Word, NameValueStr: "reason"), FutureWarnFollowing), gated!( @@ -181,16 +185,17 @@ macro_rules! experimental { // ABI, linking, symbols, and FFI ungated!( link, Normal, - template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#), + template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...", /*opt*/ import_name_type = "decorated|noprefix|undecorated""#), DuplicatesOk, ), ungated!(link_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), ungated!(no_link, Normal, template!(Word), WarnFollowing), - ungated!(repr, Normal, template!(List: "C"), DuplicatesOk), + ungated!(repr, Normal, template!(List: "C"), DuplicatesOk, @only_local: true), ungated!(export_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), ungated!(link_section, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), ungated!(no_mangle, Normal, template!(Word), WarnFollowing, @only_local: true), ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing, @only_local: true), + ungated!(link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding), // Limits: ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), @@ -201,6 +206,7 @@ macro_rules! experimental { ), // Entry point: + gated!(unix_sigpipe, Normal, template!(Word, NameValueStr: "inherit|sig_ign|sig_dfl"), ErrorFollowing, experimental!(unix_sigpipe)), ungated!(start, Normal, template!(Word), WarnFollowing), ungated!(no_start, CrateLevel, template!(Word), WarnFollowing), ungated!(no_main, CrateLevel, template!(Word), WarnFollowing), @@ -222,11 +228,15 @@ macro_rules! experimental { ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing, @only_local: true), ungated!(cold, Normal, template!(Word), WarnFollowing, @only_local: true), ungated!(no_builtins, CrateLevel, template!(Word), WarnFollowing), - ungated!(target_feature, Normal, template!(List: r#"enable = "name""#), DuplicatesOk), + ungated!( + target_feature, Normal, template!(List: r#"enable = "name""#), + DuplicatesOk, @only_local: true, + ), ungated!(track_caller, Normal, template!(Word), WarnFollowing), + ungated!(instruction_set, Normal, template!(List: "set"), ErrorPreceding), gated!( no_sanitize, Normal, - template!(List: "address, memory, thread"), DuplicatesOk, + template!(List: "address, kcfi, memory, thread"), DuplicatesOk, experimental!(no_sanitize) ), gated!(no_coverage, Normal, template!(Word), WarnFollowing, experimental!(no_coverage)), @@ -235,25 +245,23 @@ macro_rules! experimental { doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk ), + // Debugging + ungated!( + debugger_visualizer, Normal, + template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), DuplicatesOk + ), + // ========================================================================== // Unstable attributes: // ========================================================================== - // RFC #3191: #[debugger_visualizer] support - gated!( - debugger_visualizer, Normal, template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), - DuplicatesOk, experimental!(debugger_visualizer) - ), - // Linking: - gated!(naked, Normal, template!(Word), WarnFollowing, @only_local: true, naked_functions, experimental!(naked)), gated!( - link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding, raw_dylib, - experimental!(link_ordinal) + naked, Normal, template!(Word), WarnFollowing, @only_local: true, + naked_functions, experimental!(naked) ), // Plugins: - // XXX Modified for use in rust-analyzer // BuiltinAttribute { // name: sym::plugin, // only_local: false, @@ -270,10 +278,6 @@ macro_rules! experimental { // cfg_fn!(plugin) // ), // }, - BuiltinAttribute { - name: "plugin", - template: template!(List: "name"), - }, // Testing: gated!( @@ -282,7 +286,8 @@ macro_rules! experimental { ), // RFC #1268 gated!( - marker, Normal, template!(Word), WarnFollowing, marker_trait_attr, experimental!(marker) + marker, Normal, template!(Word), WarnFollowing, @only_local: true, + marker_trait_attr, experimental!(marker) ), gated!( thread_local, Normal, template!(Word), WarnFollowing, @@ -294,21 +299,12 @@ macro_rules! experimental { optimize, Normal, template!(List: "size|speed"), ErrorPreceding, optimize_attribute, experimental!(optimize), ), - // RFC 2867 - gated!( - instruction_set, Normal, template!(List: "set"), ErrorPreceding, - isa_attribute, experimental!(instruction_set) - ), gated!( ffi_returns_twice, Normal, template!(Word), WarnFollowing, experimental!(ffi_returns_twice) ), gated!(ffi_pure, Normal, template!(Word), WarnFollowing, experimental!(ffi_pure)), gated!(ffi_const, Normal, template!(Word), WarnFollowing, experimental!(ffi_const)), - gated!( - register_attr, CrateLevel, template!(List: "attr1, attr2, ..."), DuplicatesOk, - experimental!(register_attr), - ), gated!( register_tool, CrateLevel, template!(List: "tool1, tool2, ..."), DuplicatesOk, experimental!(register_tool), @@ -321,7 +317,7 @@ macro_rules! experimental { // RFC 2632 gated!( const_trait, Normal, template!(Word), WarnFollowing, const_trait_impl, - "`const` is a temporary placeholder for marking a trait that is suitable for `const` \ + "`const_trait` is a temporary placeholder for marking a trait that is suitable for `const` \ `impls` and all default bodies as `const`, which may be removed or renamed in the \ future." ), @@ -331,22 +327,47 @@ macro_rules! experimental { experimental!(deprecated_safe), ), + // `#[collapse_debuginfo]` + gated!( + collapse_debuginfo, Normal, template!(Word), WarnFollowing, + experimental!(collapse_debuginfo) + ), + + // RFC 2397 + gated!(do_not_recommend, Normal, template!(Word), WarnFollowing, experimental!(do_not_recommend)), + + // `#[cfi_encoding = ""]` + gated!( + cfi_encoding, Normal, template!(NameValueStr: "encoding"), ErrorPreceding, + experimental!(cfi_encoding) + ), + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== - ungated!(feature, CrateLevel, template!(List: "name1, name2, ..."), DuplicatesOk), + ungated!( + feature, CrateLevel, + template!(List: "name1, name2, ..."), DuplicatesOk, @only_local: true, + ), // DuplicatesOk since it has its own validation ungated!( - stable, Normal, template!(List: r#"feature = "name", since = "version""#), DuplicatesOk, + stable, Normal, + template!(List: r#"feature = "name", since = "version""#), DuplicatesOk, @only_local: true, ), ungated!( unstable, Normal, template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk, ), ungated!(rustc_const_unstable, Normal, template!(List: r#"feature = "name""#), DuplicatesOk), - ungated!(rustc_const_stable, Normal, template!(List: r#"feature = "name""#), DuplicatesOk), - ungated!(rustc_safe_intrinsic, Normal, template!(List: r#"feature = "name""#), DuplicatesOk), + ungated!( + rustc_const_stable, Normal, + template!(List: r#"feature = "name""#), DuplicatesOk, @only_local: true, + ), + ungated!( + rustc_default_body_unstable, Normal, + template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk + ), gated!( allow_internal_unstable, Normal, template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, "allow_internal_unstable side-steps feature gating and stability checks", @@ -360,6 +381,10 @@ macro_rules! experimental { allow_internal_unsafe, Normal, template!(Word), WarnFollowing, "allow_internal_unsafe side-steps the unsafe_code lint", ), + ungated!(rustc_safe_intrinsic, Normal, template!(Word), DuplicatesOk), + rustc_attr!(rustc_allowed_through_unstable_modules, Normal, template!(Word), WarnFollowing, + "rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \ + through unstable paths"), // ========================================================================== // Internal attributes: Type system related: @@ -377,10 +402,9 @@ macro_rules! experimental { rustc_attr!(rustc_allocator, 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) - ), + rustc_attr!(rustc_reallocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_deallocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), gated!( default_lib_allocator, Normal, template!(Word), WarnFollowing, allocator_internals, experimental!(default_lib_allocator), @@ -461,6 +485,12 @@ macro_rules! experimental { // Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints // to assist in changes to diagnostic APIs. rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions` + // types (as well as any others in future). + rustc_attr!(rustc_lint_opt_ty, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + // Used by the `rustc::bad_opt_access` lint on fields + // types (as well as any others in future). + rustc_attr!(rustc_lint_opt_deny_field_access, Normal, template!(List: "message"), WarnFollowing, INTERNAL_UNSTABLE), // ========================================================================== // Internal attributes, Const related: @@ -504,18 +534,25 @@ macro_rules! experimental { "language items are subject to change", ), rustc_attr!( - rustc_pass_by_value, Normal, - template!(Word), ErrorFollowing, + rustc_pass_by_value, Normal, template!(Word), ErrorFollowing, "#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference." ), rustc_attr!( rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true, "#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`." ), + rustc_attr!( + rustc_coinductive, AttributeType::Normal, template!(Word), WarnFollowing, @only_local: true, + "#![rustc_coinductive] changes a trait to be coinductive, allowing cycles in the trait solver." + ), rustc_attr!( rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true, "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl." ), + rustc_attr!( + rustc_deny_explicit_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: false, + "#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls" + ), rustc_attr!( rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), ErrorFollowing, "#[rustc_has_incoherent_inherent_impls] allows the addition of incoherent inherent impls for \ @@ -527,24 +564,20 @@ macro_rules! experimental { and it is only intended to be used in `alloc`." ), - // modified for r-a - // BuiltinAttribute { - // name: sym::rustc_diagnostic_item, - // // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`. - // only_local: false, - // type_: Normal, - // template: template!(NameValueStr: "name"), - // duplicates: ErrorFollowing, - // gate: Gated( - // Stability::Unstable, - // sym::rustc_attrs, - // "diagnostic items compiler internal support for linting", - // cfg_fn!(rustc_attrs), - // ), - // }, BuiltinAttribute { + // name: sym::rustc_diagnostic_item, name: "rustc_diagnostic_item", + // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`. + // only_local: false, + // type_: Normal, template: template!(NameValueStr: "name"), + // duplicates: ErrorFollowing, + // gate: Gated( + // Stability::Unstable, + // sym::rustc_attrs, + // "diagnostic items compiler internal support for linting", + // cfg_fn!(rustc_attrs), + // ), }, gated!( // Used in resolve: @@ -568,7 +601,7 @@ macro_rules! experimental { for reserving for `for From for T` impl" ), rustc_attr!( - rustc_test_marker, Normal, template!(Word), WarnFollowing, + rustc_test_marker, Normal, template!(NameValueStr: "name"), WarnFollowing, "the `#[rustc_test_marker]` attribute is used internally to track tests", ), rustc_attr!( @@ -594,11 +627,16 @@ macro_rules! experimental { definition of a trait, it's currently in experimental form and should be changed before \ being exposed outside of the std" ), + rustc_attr!( + rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing, + r#"`rustc_doc_primitive` is a rustc internal attribute"#, + ), // ========================================================================== // Internal attributes, Testing: // ========================================================================== + rustc_attr!(TEST, rustc_effective_visibility, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_outlives, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing), @@ -639,6 +677,10 @@ macro_rules! experimental { rustc_attr!(TEST, rustc_polymorphize_error, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_def_path, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_mir, Normal, template!(List: "arg1, arg2, ..."), DuplicatesOk), + gated!( + custom_mir, Normal, template!(List: r#"dialect = "...", phase = "...""#), + ErrorFollowing, "the `#[custom_mir]` attribute is just used for the Rust test suite", + ), rustc_attr!(TEST, rustc_dump_program_clauses, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_dump_env_program_clauses, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_object_lifetime_default, Normal, template!(Word), WarnFollowing), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs new file mode 100644 index 000000000000..e4c8d446af7b --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs @@ -0,0 +1,40 @@ +//! This module contains tests for doc-expression parsing. +//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`. + +use mbe::syntax_node_to_token_tree; +use syntax::{ast, AstNode}; + +use crate::attr::{DocAtom, DocExpr}; + +fn assert_parse_result(input: &str, expected: DocExpr) { + let (tt, _) = { + let source_file = ast::SourceFile::parse(input).ok().unwrap(); + let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + syntax_node_to_token_tree(tt.syntax()) + }; + let cfg = DocExpr::parse(&tt); + assert_eq!(cfg, expected); +} + +#[test] +fn test_doc_expr_parser() { + assert_parse_result("#![doc(hidden)]", DocAtom::Flag("hidden".into()).into()); + + assert_parse_result( + r#"#![doc(alias = "foo")]"#, + DocAtom::KeyValue { key: "alias".into(), value: "foo".into() }.into(), + ); + + assert_parse_result(r#"#![doc(alias("foo"))]"#, DocExpr::Alias(["foo".into()].into())); + assert_parse_result( + r#"#![doc(alias("foo", "bar", "baz"))]"#, + DocExpr::Alias(["foo".into(), "bar".into(), "baz".into()].into()), + ); + + assert_parse_result( + r#" + #[doc(alias("Bar", "Qux"))] + struct Foo;"#, + DocExpr::Alias(["Bar".into(), "Qux".into()].into()), + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs index b70e658efd79..36626ed1a9b1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -6,267 +6,30 @@ pub mod scope; mod pretty; -use std::{ops::Index, sync::Arc}; +use std::ops::Index; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; -use drop_bomb::DropBomb; use either::Either; -use hir_expand::{ - attrs::RawAttrs, hygiene::Hygiene, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId, -}; +use hir_expand::{name::Name, HirFileId, InFile}; use la_arena::{Arena, ArenaMap}; -use limit::Limit; use profile::Count; use rustc_hash::FxHashMap; -use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr}; +use syntax::{ast, AstPtr, SyntaxNodePtr}; +use triomphe::Arc; use crate::{ - attr::Attrs, db::DefDatabase, - expr::{ + expander::Expander, + hir::{ dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId, RecordFieldPat, }, - item_scope::BuiltinShadowMode, - macro_id_to_def_id, nameres::DefMap, path::{ModPath, Path}, src::{HasChildSource, HasSource}, - AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId, - UnresolvedMacro, + BlockId, DefWithBodyId, HasModule, Lookup, }; -pub use lower::LowerCtx; - -/// A subset of Expander that only deals with cfg attributes. We only need it to -/// avoid cyclic queries in crate def map during enum processing. -#[derive(Debug)] -pub(crate) struct CfgExpander { - cfg_options: CfgOptions, - hygiene: Hygiene, - krate: CrateId, -} - -#[derive(Debug)] -pub struct Expander { - cfg_expander: CfgExpander, - def_map: Arc, - current_file_id: HirFileId, - module: LocalModuleId, - /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. - recursion_depth: usize, -} - -impl CfgExpander { - pub(crate) fn new( - db: &dyn DefDatabase, - current_file_id: HirFileId, - krate: CrateId, - ) -> CfgExpander { - let hygiene = Hygiene::new(db.upcast(), current_file_id); - let cfg_options = db.crate_graph()[krate].cfg_options.clone(); - CfgExpander { cfg_options, hygiene, krate } - } - - pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { - Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene)) - } - - pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool { - let attrs = self.parse_attrs(db, owner); - attrs.is_cfg_enabled(&self.cfg_options) - } -} - -impl Expander { - pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander { - let cfg_expander = CfgExpander::new(db, current_file_id, module.krate); - let def_map = module.def_map(db); - Expander { - cfg_expander, - def_map, - current_file_id, - module: module.local_id, - recursion_depth: 0, - } - } - - pub fn enter_expand( - &mut self, - db: &dyn DefDatabase, - macro_call: ast::MacroCall, - ) -> Result>, UnresolvedMacro> { - let mut unresolved_macro_err = None; - - let result = self.within_limit(db, |this| { - let macro_call = InFile::new(this.current_file_id, ¯o_call); - - let resolver = - |path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); - - let mut err = None; - let call_id = match macro_call.as_call_id_with_errors( - db, - this.def_map.krate(), - resolver, - &mut |e| { - err.get_or_insert(e); - }, - ) { - Ok(call_id) => call_id, - Err(resolve_err) => { - unresolved_macro_err = Some(resolve_err); - return ExpandResult { value: None, err: None }; - } - }; - ExpandResult { value: call_id.ok(), err } - }); - - if let Some(err) = unresolved_macro_err { - Err(err) - } else { - Ok(result) - } - } - - pub fn enter_expand_id( - &mut self, - db: &dyn DefDatabase, - call_id: MacroCallId, - ) -> ExpandResult> { - self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) - } - - fn enter_expand_inner( - db: &dyn DefDatabase, - call_id: MacroCallId, - mut err: Option, - ) -> ExpandResult> { - if err.is_none() { - err = db.macro_expand_error(call_id); - } - - let file_id = call_id.as_file(); - - let raw_node = match db.parse_or_expand(file_id) { - Some(it) => it, - None => { - // Only `None` if the macro expansion produced no usable AST. - if err.is_none() { - tracing::warn!("no error despite `parse_or_expand` failing"); - } - - return ExpandResult::only_err(err.unwrap_or_else(|| { - ExpandError::Other("failed to parse macro invocation".into()) - })); - } - }; - - ExpandResult { value: Some((file_id, raw_node)), err } - } - - pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { - self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); - self.current_file_id = mark.file_id; - if self.recursion_depth == usize::MAX { - // Recursion limit has been reached somewhere in the macro expansion tree. Reset the - // depth only when we get out of the tree. - if !self.current_file_id.is_macro() { - self.recursion_depth = 0; - } - } else { - self.recursion_depth -= 1; - } - mark.bomb.defuse(); - } - - pub(crate) fn to_source(&self, value: T) -> InFile { - InFile { file_id: self.current_file_id, value } - } - - pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { - self.cfg_expander.parse_attrs(db, owner) - } - - pub(crate) fn cfg_options(&self) -> &CfgOptions { - &self.cfg_expander.cfg_options - } - - pub fn current_file_id(&self) -> HirFileId { - self.current_file_id - } - - fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option { - let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene); - Path::from_src(path, &ctx) - } - - fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option { - self.def_map.resolve_path(db, self.module, path, BuiltinShadowMode::Other).0.take_macros() - } - - fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit { - let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _; - - #[cfg(not(test))] - return Limit::new(limit); - - // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug - #[cfg(test)] - return Limit::new(std::cmp::min(32, limit)); - } - - fn within_limit( - &mut self, - db: &dyn DefDatabase, - op: F, - ) -> ExpandResult> - where - F: FnOnce(&mut Self) -> ExpandResult>, - { - if self.recursion_depth == usize::MAX { - // Recursion limit has been reached somewhere in the macro expansion tree. We should - // stop expanding other macro calls in this tree, or else this may result in - // exponential number of macro expansions, leading to a hang. - // - // The overflow error should have been reported when it occurred (see the next branch), - // so don't return overflow error here to avoid diagnostics duplication. - cov_mark::hit!(overflow_but_not_me); - return ExpandResult::only_err(ExpandError::RecursionOverflowPosioned); - } else if self.recursion_limit(db).check(self.recursion_depth + 1).is_err() { - self.recursion_depth = usize::MAX; - cov_mark::hit!(your_stack_belongs_to_me); - return ExpandResult::only_err(ExpandError::Other( - "reached recursion limit during macro expansion".into(), - )); - } - - let ExpandResult { value, err } = op(self); - let Some(call_id) = value else { - return ExpandResult { value: None, err }; - }; - - Self::enter_expand_inner(db, call_id, err).map(|value| { - value.and_then(|(new_file_id, node)| { - let node = T::cast(node)?; - - self.recursion_depth += 1; - self.cfg_expander.hygiene = Hygiene::new(db.upcast(), new_file_id); - let old_file_id = std::mem::replace(&mut self.current_file_id, new_file_id); - let mark = - Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") }; - Some((mark, node)) - }) - }) - } -} - -#[derive(Debug)] -pub struct Mark { - file_id: HirFileId, - bomb: DropBomb, -} - /// The body of an item (function, const etc.). #[derive(Debug, Eq, PartialEq)] pub struct Body { @@ -343,6 +106,8 @@ pub enum BodyDiagnostic { MacroError { node: InFile>, message: String }, UnresolvedProcMacro { node: InFile>, krate: CrateId }, UnresolvedMacroCall { node: InFile>, path: ModPath }, + UnreachableLabel { node: InFile>, name: Name }, + UndeclaredLabel { node: InFile>, name: Name }, } impl Body { @@ -353,45 +118,54 @@ pub(crate) fn body_with_source_map_query( let _p = profile::span("body_with_source_map_query"); let mut params = None; - let (file_id, module, body) = match def { - DefWithBodyId::FunctionId(f) => { - let f = f.lookup(db); - let src = f.source(db); - 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(); + let (file_id, module, body, is_async_fn) = { + match def { + DefWithBodyId::FunctionId(f) => { + let data = db.function_data(f); + let f = f.lookup(db); + let src = f.source(db); + 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) + }), + ) + }); ( - 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), + data.has_async_kw(), ) - }); - (src.file_id, f.module(db), src.value.body().map(ast::Expr::from)) - } - DefWithBodyId::ConstId(c) => { - let c = c.lookup(db); - let src = c.source(db); - (src.file_id, c.module(db), src.value.body()) - } - DefWithBodyId::StaticId(s) => { - let s = s.lookup(db); - 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()) + } + DefWithBodyId::ConstId(c) => { + let c = c.lookup(db); + let src = c.source(db); + (src.file_id, c.module(db), src.value.body(), false) + } + DefWithBodyId::StaticId(s) => { + let s = s.lookup(db); + let src = s.source(db); + (src.file_id, s.module(db), src.value.body(), false) + } + 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(), false) + } } }; let expander = Expander::new(db, file_id, module); - let (mut body, source_map) = Body::new(db, expander, params, body); + let (mut body, source_map) = + Body::new(db, def, expander, params, body, module.krate, is_async_fn); body.shrink_to_fit(); (Arc::new(body), Arc::new(source_map)) @@ -406,22 +180,32 @@ pub fn blocks<'a>( &'a self, db: &'a dyn DefDatabase, ) -> impl Iterator)> + '_ { - self.block_scopes - .iter() - .map(move |&block| (block, db.block_def_map(block).expect("block ID without DefMap"))) + self.block_scopes.iter().map(move |&block| (block, db.block_def_map(block))) } pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String { pretty::print_body_hir(db, self, owner) } + pub fn pretty_print_expr( + &self, + db: &dyn DefDatabase, + owner: DefWithBodyId, + expr: ExprId, + ) -> String { + pretty::print_expr_hir(db, self, owner, expr) + } + fn new( db: &dyn DefDatabase, + owner: DefWithBodyId, expander: Expander, params: Option<(ast::ParamList, impl Iterator)>, body: Option, + krate: CrateId, + is_async_fn: bool, ) -> (Body, BodySourceMap) { - lower::lower(db, expander, params, body) + lower::lower(db, owner, expander, params, body, krate, is_async_fn) } fn shrink_to_fit(&mut self) { @@ -437,15 +221,14 @@ fn shrink_to_fit(&mut self) { pub fn walk_bindings_in_pat(&self, pat_id: PatId, mut f: impl FnMut(BindingId)) { self.walk_pats(pat_id, &mut |pat| { - if let Pat::Bind { id, .. } = pat { + if let Pat::Bind { id, .. } = &self[pat] { f(*id); } }); } - pub fn walk_pats(&self, pat_id: PatId, f: &mut impl FnMut(&Pat)) { + pub fn walk_pats_shallow(&self, pat_id: PatId, mut f: impl FnMut(PatId)) { let pat = &self[pat_id]; - f(pat); match pat { Pat::Range { .. } | Pat::Lit(..) @@ -455,23 +238,28 @@ pub fn walk_pats(&self, pat_id: PatId, f: &mut impl FnMut(&Pat)) { | Pat::Missing => {} &Pat::Bind { subpat, .. } => { if let Some(subpat) = subpat { - self.walk_pats(subpat, f); + f(subpat); } } Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => { - args.iter().copied().for_each(|p| self.walk_pats(p, f)); + args.iter().copied().for_each(|p| f(p)); } - Pat::Ref { pat, .. } => self.walk_pats(*pat, f), + Pat::Ref { pat, .. } => f(*pat), Pat::Slice { prefix, slice, suffix } => { let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter()); - total_iter.copied().for_each(|p| self.walk_pats(p, f)); + total_iter.copied().for_each(|p| f(p)); } Pat::Record { args, .. } => { - args.iter().for_each(|RecordFieldPat { pat, .. }| self.walk_pats(*pat, f)); + args.iter().for_each(|RecordFieldPat { pat, .. }| f(*pat)); } - Pat::Box { inner } => self.walk_pats(*inner, f), + Pat::Box { inner } => f(*inner), } } + + pub fn walk_pats(&self, pat_id: PatId, f: &mut impl FnMut(PatId)) { + f(pat_id); + self.walk_pats_shallow(pat_id, |p| self.walk_pats(p, f)); + } } impl Default for Body { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs index fedaf3955985..7b88e525bf18 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs @@ -1,88 +1,62 @@ //! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr` //! representation. -use std::{mem, sync::Arc}; +use std::mem; +use base_db::CrateId; use either::Either; use hir_expand::{ ast_id_map::AstIdMap, - hygiene::Hygiene, name::{name, AsName, Name}, - AstId, ExpandError, HirFileId, InFile, + AstId, ExpandError, InFile, }; use intern::Interned; use la_arena::Arena; -use once_cell::unsync::OnceCell; use profile::Count; use rustc_hash::FxHashMap; use smallvec::SmallVec; use syntax::{ ast::{ - self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind, + self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasLoopBody, HasName, SlicePatComponents, }, AstNode, AstPtr, SyntaxNodePtr, }; +use triomphe::Arc; use crate::{ - adt::StructKind, - body::{Body, BodySourceMap, Expander, ExprPtr, LabelPtr, LabelSource, PatPtr}, - body::{BodyDiagnostic, ExprSource, PatSource}, - builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, + body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr}, + data::adt::StructKind, db::DefDatabase, - expr::{ - dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, ClosureKind, Expr, ExprId, - FloatTypeWrapper, Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, - RecordFieldPat, RecordLitField, Statement, + expander::Expander, + hir::{ + dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, + ClosureKind, Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, + Pat, PatId, RecordFieldPat, RecordLitField, Statement, }, item_scope::BuiltinShadowMode, + lang_item::LangItem, + lower::LowerCtx, + nameres::{DefMap, MacroSubNs}, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, - AdtId, BlockId, BlockLoc, ModuleDefId, UnresolvedMacro, + AdtId, BlockId, BlockLoc, DefWithBodyId, ModuleDefId, UnresolvedMacro, }; -pub struct LowerCtx<'a> { - pub db: &'a dyn DefDatabase, - hygiene: Hygiene, - ast_id_map: Option<(HirFileId, OnceCell>)>, -} - -impl<'a> LowerCtx<'a> { - pub fn new(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self { - LowerCtx { - db, - hygiene: Hygiene::new(db.upcast(), file_id), - ast_id_map: Some((file_id, OnceCell::new())), - } - } - - pub fn with_hygiene(db: &'a dyn DefDatabase, hygiene: &Hygiene) -> Self { - LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: None } - } - - pub(crate) fn hygiene(&self) -> &Hygiene { - &self.hygiene - } - - pub(crate) fn lower_path(&self, ast: ast::Path) -> Option { - Path::from_src(ast, self) - } - - pub(crate) fn ast_id(&self, item: &N) -> Option> { - let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?; - let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id)); - Some(InFile::new(file_id, ast_id_map.ast_id(item))) - } -} - pub(super) fn lower( db: &dyn DefDatabase, + owner: DefWithBodyId, expander: Expander, params: Option<(ast::ParamList, impl Iterator)>, body: Option, + krate: CrateId, + is_async_fn: bool, ) -> (Body, BodySourceMap) { ExprCollector { db, + owner, + krate, + def_map: expander.module.def_map(db), source_map: BodySourceMap::default(), ast_id_map: db.ast_id_map(expander.current_file_id), body: Body { @@ -96,25 +70,79 @@ pub(super) fn lower( _c: Count::new(), }, expander, + current_try_block_label: None, is_lowering_assignee_expr: false, is_lowering_generator: false, + label_ribs: Vec::new(), + current_binding_owner: None, } - .collect(params, body) + .collect(params, body, is_async_fn) } struct ExprCollector<'a> { db: &'a dyn DefDatabase, expander: Expander, + owner: DefWithBodyId, + def_map: Arc, ast_id_map: Arc, + krate: CrateId, body: Body, source_map: BodySourceMap, + is_lowering_assignee_expr: bool, is_lowering_generator: bool, + + current_try_block_label: Option, + // points to the expression that a try expression will target (replaces current_try_block_label) + // catch_scope: Option, + // points to the expression that an unlabeled control flow will target + // loop_scope: Option, + // needed to diagnose non label control flow in while conditions + // is_in_loop_condition: bool, + + // resolution + label_ribs: Vec, + current_binding_owner: Option, +} + +#[derive(Clone, Debug)] +struct LabelRib { + kind: RibKind, + // Once we handle macro hygiene this will need to be a map + label: Option<(Name, LabelId)>, +} + +impl LabelRib { + fn new(kind: RibKind) -> Self { + LabelRib { kind, label: None } + } + fn new_normal(label: (Name, LabelId)) -> Self { + LabelRib { kind: RibKind::Normal, label: Some(label) } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum RibKind { + Normal, + Closure, + Constant, +} + +impl RibKind { + /// This rib forbids referring to labels defined in upwards ribs. + fn is_label_barrier(self) -> bool { + match self { + RibKind::Normal => false, + RibKind::Closure | RibKind::Constant => true, + } + } } #[derive(Debug, Default)] struct BindingList { map: FxHashMap, + is_used: FxHashMap, + reject_new: bool, } impl BindingList { @@ -124,7 +152,27 @@ fn find( name: Name, mode: BindingAnnotation, ) -> BindingId { - *self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode)) + let id = *self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode)); + if ec.body.bindings[id].mode != mode { + ec.body.bindings[id].problems = Some(BindingProblems::BoundInconsistently); + } + self.check_is_used(ec, id); + id + } + + fn check_is_used(&mut self, ec: &mut ExprCollector<'_>, id: BindingId) { + match self.is_used.get(&id) { + None => { + if self.reject_new { + ec.body.bindings[id].problems = Some(BindingProblems::NotBoundAcrossAll); + } + } + Some(true) => { + ec.body.bindings[id].problems = Some(BindingProblems::BoundMoreThanOnce); + } + Some(false) => {} + } + self.is_used.insert(id, true); } } @@ -133,6 +181,7 @@ fn collect( mut self, param_list: Option<(ast::ParamList, impl Iterator)>, body: Option, + is_async_fn: bool, ) -> (Body, BodySourceMap) { if let Some((param_list, mut attr_enabled)) = param_list { if let Some(self_param) = @@ -152,72 +201,35 @@ fn collect( self.body.params.push(param_pat); } - for pat in param_list - .params() - .zip(attr_enabled) - .filter_map(|(param, enabled)| param.pat().filter(|_| enabled)) + for (param, _) in param_list.params().zip(attr_enabled).filter(|(_, enabled)| *enabled) { - let param_pat = self.collect_pat(pat); + let param_pat = self.collect_pat_top(param.pat()); self.body.params.push(param_pat); } }; + self.body.body_expr = self.with_label_rib(RibKind::Closure, |this| { + if is_async_fn { + match body { + Some(e) => { + let expr = this.collect_expr(e); + this.alloc_expr_desugared(Expr::Async { + id: None, + statements: Box::new([]), + tail: Some(expr), + }) + } + None => this.missing_expr(), + } + } else { + this.collect_expr_opt(body) + } + }); - self.body.body_expr = self.collect_expr_opt(body); (self.body, self.source_map) } fn ctx(&self) -> LowerCtx<'_> { - LowerCtx::new(self.db, self.expander.current_file_id) - } - - fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { - let src = self.expander.to_source(ptr); - let id = self.make_expr(expr, src.clone()); - self.source_map.expr_map.insert(src, id); - id - } - // desugared exprs don't have ptr, that's wrong and should be fixed - // somehow. - fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { - self.body.exprs.alloc(expr) - } - fn missing_expr(&mut self) -> ExprId { - self.alloc_expr_desugared(Expr::Missing) - } - fn make_expr(&mut self, expr: Expr, src: ExprSource) -> ExprId { - let id = self.body.exprs.alloc(expr); - self.source_map.expr_map_back.insert(id, src); - id - } - - fn alloc_binding(&mut self, name: Name, mode: BindingAnnotation) -> BindingId { - self.body.bindings.alloc(Binding { name, mode, definitions: SmallVec::new() }) - } - fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { - let src = self.expander.to_source(ptr); - let id = self.make_pat(pat, src.clone()); - self.source_map.pat_map.insert(src, id); - id - } - fn missing_pat(&mut self) -> PatId { - self.body.pats.alloc(Pat::Missing) - } - fn make_pat(&mut self, pat: Pat, src: PatSource) -> PatId { - let id = self.body.pats.alloc(pat); - self.source_map.pat_map_back.insert(id, src); - id - } - - fn alloc_label(&mut self, label: Label, ptr: LabelPtr) -> LabelId { - let src = self.expander.to_source(ptr); - let id = self.make_label(label, src.clone()); - self.source_map.label_map.insert(src, id); - id - } - fn make_label(&mut self, label: Label, src: LabelSource) -> LabelId { - let id = self.body.labels.alloc(label); - self.source_map.label_map_back.insert(id, src); - id + self.expander.ctx(self.db) } fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { @@ -229,6 +241,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { let syntax_ptr = AstPtr::new(&expr); self.check_cfg(&expr)?; + // FIXME: Move some of these arms out into separate methods for clarity Some(match expr { ast::Expr::IfExpr(e) => { let then_branch = self.collect_block_opt(e.then_branch()); @@ -246,18 +259,12 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr) } ast::Expr::LetExpr(e) => { - let pat = self.collect_pat_opt(e.pat()); + let pat = self.collect_pat_top(e.pat()); let expr = self.collect_expr_opt(e.expr()); self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr) } ast::Expr::BlockExpr(e) => match e.modifier() { - Some(ast::BlockModifier::Try(_)) => { - self.collect_block_(e, |id, statements, tail| Expr::TryBlock { - id, - statements, - tail, - }) - } + Some(ast::BlockModifier::Try(_)) => self.desugar_try_block(e), Some(ast::BlockModifier::Unsafe(_)) => { self.collect_block_(e, |id, statements, tail| Expr::Unsafe { id, @@ -267,50 +274,74 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { } Some(ast::BlockModifier::Label(label)) => { let label = self.collect_label(label); - self.collect_block_(e, |id, statements, tail| Expr::Block { - id, - statements, - tail, - label: Some(label), + self.with_labeled_rib(label, |this| { + this.collect_block_(e, |id, statements, tail| Expr::Block { + id, + statements, + tail, + label: Some(label), + }) + }) + } + Some(ast::BlockModifier::Async(_)) => { + self.with_label_rib(RibKind::Closure, |this| { + this.collect_block_(e, |id, statements, tail| Expr::Async { + id, + statements, + tail, + }) + }) + } + Some(ast::BlockModifier::Const(_)) => { + self.with_label_rib(RibKind::Constant, |this| { + let (result_expr_id, prev_binding_owner) = + this.initialize_binding_owner(syntax_ptr); + let inner_expr = this.collect_block(e); + let x = this.db.intern_anonymous_const((this.owner, inner_expr)); + this.body.exprs[result_expr_id] = Expr::Const(x); + this.current_binding_owner = prev_binding_owner; + result_expr_id }) } - Some(ast::BlockModifier::Async(_)) => self - .collect_block_(e, |id, statements, tail| Expr::Async { id, statements, tail }), - Some(ast::BlockModifier::Const(_)) => self - .collect_block_(e, |id, statements, tail| Expr::Const { id, statements, tail }), None => self.collect_block(e), }, ast::Expr::LoopExpr(e) => { let label = e.label().map(|label| self.collect_label(label)); - let body = self.collect_block_opt(e.loop_body()); + let body = self.collect_labelled_block_opt(label, e.loop_body()); self.alloc_expr(Expr::Loop { body, label }, syntax_ptr) } ast::Expr::WhileExpr(e) => { let label = e.label().map(|label| self.collect_label(label)); - let body = self.collect_block_opt(e.loop_body()); - + let body = self.collect_labelled_block_opt(label, e.loop_body()); let condition = self.collect_expr_opt(e.condition()); self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr) } - ast::Expr::ForExpr(e) => { - let label = e.label().map(|label| self.collect_label(label)); - let iterable = self.collect_expr_opt(e.iterable()); - let pat = self.collect_pat_opt(e.pat()); - let body = self.collect_block_opt(e.loop_body()); - self.alloc_expr(Expr::For { iterable, pat, body, label }, syntax_ptr) - } + ast::Expr::ForExpr(e) => self.collect_for_loop(syntax_ptr, e), ast::Expr::CallExpr(e) => { - let callee = self.collect_expr_opt(e.expr()); - let args = if let Some(arg_list) = e.arg_list() { - arg_list.args().filter_map(|e| self.maybe_collect_expr(e)).collect() - } else { - Box::default() + let is_rustc_box = { + let attrs = e.attrs(); + attrs.filter_map(|x| x.as_simple_atom()).any(|x| x == "rustc_box") }; - self.alloc_expr( - Expr::Call { callee, args, is_assignee_expr: self.is_lowering_assignee_expr }, - syntax_ptr, - ) + if is_rustc_box { + let expr = self.collect_expr_opt(e.arg_list().and_then(|x| x.args().next())); + self.alloc_expr(Expr::Box { expr }, syntax_ptr) + } else { + let callee = self.collect_expr_opt(e.expr()); + let args = if let Some(arg_list) = e.arg_list() { + arg_list.args().filter_map(|e| self.maybe_collect_expr(e)).collect() + } else { + Box::default() + }; + self.alloc_expr( + Expr::Call { + callee, + args, + is_assignee_expr: self.is_lowering_assignee_expr, + }, + syntax_ptr, + ) + } } ast::Expr::MethodCallExpr(e) => { let receiver = self.collect_expr_opt(e.receiver()); @@ -336,7 +367,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { .arms() .filter_map(|arm| { self.check_cfg(&arm).map(|()| MatchArm { - pat: self.collect_pat_opt(arm.pat()), + pat: self.collect_pat_top(arm.pat()), expr: self.collect_expr_opt(arm.expr()), guard: arm .guard() @@ -357,16 +388,20 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { .unwrap_or(Expr::Missing); self.alloc_expr(path, syntax_ptr) } - ast::Expr::ContinueExpr(e) => self.alloc_expr( - Expr::Continue { label: e.lifetime().map(|l| Name::new_lifetime(&l)) }, - syntax_ptr, - ), + ast::Expr::ContinueExpr(e) => { + let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| { + self.source_map.diagnostics.push(e); + None + }); + self.alloc_expr(Expr::Continue { label }, syntax_ptr) + } ast::Expr::BreakExpr(e) => { + let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| { + self.source_map.diagnostics.push(e); + None + }); let expr = e.expr().map(|e| self.collect_expr(e)); - self.alloc_expr( - Expr::Break { expr, label: e.lifetime().map(|l| Name::new_lifetime(&l)) }, - syntax_ptr, - ) + self.alloc_expr(Expr::Break { expr, label }, syntax_ptr) } ast::Expr::ParenExpr(e) => { let inner = self.collect_expr_opt(e.expr()); @@ -437,10 +472,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { let expr = self.collect_expr_opt(e.expr()); self.alloc_expr(Expr::Await { expr }, syntax_ptr) } - ast::Expr::TryExpr(e) => { - let expr = self.collect_expr_opt(e.expr()); - self.alloc_expr(Expr::Try { expr }, syntax_ptr) - } + ast::Expr::TryExpr(e) => self.collect_try_operator(syntax_ptr, e), ast::Expr::CastExpr(e) => { let expr = self.collect_expr_opt(e.expr()); let type_ref = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty())); @@ -470,14 +502,16 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { None => self.alloc_expr(Expr::Missing, syntax_ptr), } } - ast::Expr::ClosureExpr(e) => { + ast::Expr::ClosureExpr(e) => self.with_label_rib(RibKind::Closure, |this| { + let (result_expr_id, prev_binding_owner) = + this.initialize_binding_owner(syntax_ptr); let mut args = Vec::new(); let mut arg_types = Vec::new(); if let Some(pl) = e.param_list() { for param in pl.params() { - let pat = self.collect_pat_opt(param.pat()); + let pat = this.collect_pat_top(param.pat()); let type_ref = - param.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); + param.ty().map(|it| Interned::new(TypeRef::from_ast(&this.ctx(), it))); args.push(pat); arg_types.push(type_ref); } @@ -485,14 +519,14 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { let ret_type = e .ret_type() .and_then(|r| r.ty()) - .map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); + .map(|it| Interned::new(TypeRef::from_ast(&this.ctx(), it))); - let prev_is_lowering_generator = self.is_lowering_generator; - self.is_lowering_generator = false; + let prev_is_lowering_generator = mem::take(&mut this.is_lowering_generator); + let prev_try_block_label = this.current_try_block_label.take(); - let body = self.collect_expr_opt(e.body()); + let body = this.collect_expr_opt(e.body()); - let closure_kind = if self.is_lowering_generator { + let closure_kind = if this.is_lowering_generator { let movability = if e.static_token().is_some() { Movability::Static } else { @@ -504,19 +538,21 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { } 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, - ) - } + let capture_by = + if e.move_token().is_some() { CaptureBy::Value } else { CaptureBy::Ref }; + this.is_lowering_generator = prev_is_lowering_generator; + this.current_binding_owner = prev_binding_owner; + this.current_try_block_label = prev_try_block_label; + this.body.exprs[result_expr_id] = Expr::Closure { + args: args.into(), + arg_types: arg_types.into(), + ret_type, + body, + closure_kind, + capture_by, + }; + result_expr_id + }), ast::Expr::BinExpr(e) => { let op = e.op_kind(); if let Some(ast::BinaryOp::Assignment { op: None }) = op { @@ -528,9 +564,18 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr) } ast::Expr::TupleExpr(e) => { - let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect(); + let mut exprs: Vec<_> = e.fields().map(|expr| self.collect_expr(expr)).collect(); + // if there is a leading comma, the user is most likely to type out a leading expression + // so we insert a missing expression at the beginning for IDE features + if comma_follows_token(e.l_paren_token()) { + exprs.insert(0, self.missing_expr()); + } + self.alloc_expr( - Expr::Tuple { exprs, is_assignee_expr: self.is_lowering_assignee_expr }, + Expr::Tuple { + exprs: exprs.into_boxed_slice(), + is_assignee_expr: self.is_lowering_assignee_expr, + }, syntax_ptr, ) } @@ -555,7 +600,17 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { } ArrayExprKind::Repeat { initializer, repeat } => { let initializer = self.collect_expr_opt(initializer); - let repeat = self.collect_expr_opt(repeat); + let repeat = self.with_label_rib(RibKind::Constant, |this| { + if let Some(repeat) = repeat { + let syntax_ptr = AstPtr::new(&repeat); + this.collect_as_a_binding_owner_bad( + |this| this.collect_expr(repeat), + syntax_ptr, + ) + } else { + this.missing_expr() + } + }); self.alloc_expr( Expr::Array(Array::Repeat { initializer, repeat }), syntax_ptr, @@ -601,6 +656,244 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { }) } + fn initialize_binding_owner( + &mut self, + syntax_ptr: AstPtr, + ) -> (ExprId, Option) { + let result_expr_id = self.alloc_expr(Expr::Missing, syntax_ptr); + let prev_binding_owner = self.current_binding_owner.take(); + self.current_binding_owner = Some(result_expr_id); + (result_expr_id, prev_binding_owner) + } + + /// FIXME: This function is bad. It will produce a dangling `Missing` expr which wastes memory. Currently + /// it is used only for const blocks and repeat expressions, which are also hacky and ideally should have + /// their own body. Don't add more usage for this function so that we can remove this function after + /// separating those bodies. + fn collect_as_a_binding_owner_bad( + &mut self, + job: impl FnOnce(&mut ExprCollector<'_>) -> ExprId, + syntax_ptr: AstPtr, + ) -> ExprId { + let (id, prev_owner) = self.initialize_binding_owner(syntax_ptr); + let tmp = job(self); + self.body.exprs[id] = mem::replace(&mut self.body.exprs[tmp], Expr::Missing); + self.current_binding_owner = prev_owner; + id + } + + /// Desugar `try { ; }` into `': { ; ::std::ops::Try::from_output() }`, + /// `try { ; }` into `': { ; ::std::ops::Try::from_output(()) }` + /// and save the `` to use it as a break target for desugaring of the `?` operator. + fn desugar_try_block(&mut self, e: BlockExpr) -> ExprId { + let Some(try_from_output) = LangItem::TryTraitFromOutput.path(self.db, self.krate) else { + return self.collect_block(e); + }; + let label = self.alloc_label_desugared(Label { name: Name::generate_new_name() }); + let old_label = self.current_try_block_label.replace(label); + + let (btail, expr_id) = self.with_labeled_rib(label, |this| { + let mut btail = None; + let block = this.collect_block_(e, |id, statements, tail| { + btail = tail; + Expr::Block { id, statements, tail, label: Some(label) } + }); + (btail, block) + }); + + let callee = self.alloc_expr_desugared(Expr::Path(try_from_output)); + let next_tail = match btail { + Some(tail) => self.alloc_expr_desugared(Expr::Call { + callee, + args: Box::new([tail]), + is_assignee_expr: false, + }), + None => { + let unit = self.alloc_expr_desugared(Expr::Tuple { + exprs: Box::new([]), + is_assignee_expr: false, + }); + self.alloc_expr_desugared(Expr::Call { + callee, + args: Box::new([unit]), + is_assignee_expr: false, + }) + } + }; + let Expr::Block { tail, .. } = &mut self.body.exprs[expr_id] else { + unreachable!("block was lowered to non-block"); + }; + *tail = Some(next_tail); + self.current_try_block_label = old_label; + expr_id + } + + /// Desugar `ast::ForExpr` from: `[opt_ident]: for in ` into: + /// ```ignore (pseudo-rust) + /// match IntoIterator::into_iter() { + /// mut iter => { + /// [opt_ident]: loop { + /// match Iterator::next(&mut iter) { + /// None => break, + /// Some() => , + /// }; + /// } + /// } + /// } + /// ``` + fn collect_for_loop(&mut self, syntax_ptr: AstPtr, e: ast::ForExpr) -> ExprId { + let (into_iter_fn, iter_next_fn, option_some, option_none) = 'if_chain: { + if let Some(into_iter_fn) = LangItem::IntoIterIntoIter.path(self.db, self.krate) { + if let Some(iter_next_fn) = LangItem::IteratorNext.path(self.db, self.krate) { + if let Some(option_some) = LangItem::OptionSome.path(self.db, self.krate) { + if let Some(option_none) = LangItem::OptionNone.path(self.db, self.krate) { + break 'if_chain (into_iter_fn, iter_next_fn, option_some, option_none); + } + } + } + } + // Some of the needed lang items are missing, so we can't desugar + return self.alloc_expr(Expr::Missing, syntax_ptr); + }; + let head = self.collect_expr_opt(e.iterable()); + let into_iter_fn_expr = self.alloc_expr(Expr::Path(into_iter_fn), syntax_ptr.clone()); + let iterator = self.alloc_expr( + Expr::Call { + callee: into_iter_fn_expr, + args: Box::new([head]), + is_assignee_expr: false, + }, + syntax_ptr.clone(), + ); + let none_arm = MatchArm { + pat: self.alloc_pat_desugared(Pat::Path(Box::new(option_none))), + guard: None, + expr: self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone()), + }; + let some_pat = Pat::TupleStruct { + path: Some(Box::new(option_some)), + args: Box::new([self.collect_pat_top(e.pat())]), + ellipsis: None, + }; + let label = e.label().map(|label| self.collect_label(label)); + let some_arm = MatchArm { + pat: self.alloc_pat_desugared(some_pat), + guard: None, + expr: self.with_opt_labeled_rib(label, |this| { + this.collect_expr_opt(e.loop_body().map(|x| x.into())) + }), + }; + let iter_name = Name::generate_new_name(); + let iter_binding = self.alloc_binding(iter_name.clone(), BindingAnnotation::Mutable); + let iter_expr = self.alloc_expr(Expr::Path(Path::from(iter_name)), syntax_ptr.clone()); + let iter_expr_mut = self.alloc_expr( + Expr::Ref { expr: iter_expr, rawness: Rawness::Ref, mutability: Mutability::Mut }, + syntax_ptr.clone(), + ); + let iter_next_fn_expr = self.alloc_expr(Expr::Path(iter_next_fn), syntax_ptr.clone()); + let iter_next_expr = self.alloc_expr( + Expr::Call { + callee: iter_next_fn_expr, + args: Box::new([iter_expr_mut]), + is_assignee_expr: false, + }, + syntax_ptr.clone(), + ); + let loop_inner = self.alloc_expr( + Expr::Match { expr: iter_next_expr, arms: Box::new([none_arm, some_arm]) }, + syntax_ptr.clone(), + ); + let loop_outer = + self.alloc_expr(Expr::Loop { body: loop_inner, label }, syntax_ptr.clone()); + let iter_pat = self.alloc_pat_desugared(Pat::Bind { id: iter_binding, subpat: None }); + self.alloc_expr( + Expr::Match { + expr: iterator, + arms: Box::new([MatchArm { pat: iter_pat, guard: None, expr: loop_outer }]), + }, + syntax_ptr.clone(), + ) + } + + /// Desugar `ast::TryExpr` from: `?` into: + /// ```ignore (pseudo-rust) + /// match Try::branch() { + /// ControlFlow::Continue(val) => val, + /// ControlFlow::Break(residual) => + /// // If there is an enclosing `try {...}`: + /// break 'catch_target Try::from_residual(residual), + /// // Otherwise: + /// return Try::from_residual(residual), + /// } + /// ``` + fn collect_try_operator(&mut self, syntax_ptr: AstPtr, e: ast::TryExpr) -> ExprId { + let (try_branch, cf_continue, cf_break, try_from_residual) = 'if_chain: { + if let Some(try_branch) = LangItem::TryTraitBranch.path(self.db, self.krate) { + if let Some(cf_continue) = LangItem::ControlFlowContinue.path(self.db, self.krate) { + if let Some(cf_break) = LangItem::ControlFlowBreak.path(self.db, self.krate) { + if let Some(try_from_residual) = + LangItem::TryTraitFromResidual.path(self.db, self.krate) + { + break 'if_chain (try_branch, cf_continue, cf_break, try_from_residual); + } + } + } + } + // Some of the needed lang items are missing, so we can't desugar + return self.alloc_expr(Expr::Missing, syntax_ptr); + }; + let operand = self.collect_expr_opt(e.expr()); + let try_branch = self.alloc_expr(Expr::Path(try_branch), syntax_ptr.clone()); + let expr = self.alloc_expr( + Expr::Call { callee: try_branch, args: Box::new([operand]), is_assignee_expr: false }, + syntax_ptr.clone(), + ); + let continue_name = Name::generate_new_name(); + let continue_binding = + self.alloc_binding(continue_name.clone(), BindingAnnotation::Unannotated); + let continue_bpat = + self.alloc_pat_desugared(Pat::Bind { id: continue_binding, subpat: None }); + self.add_definition_to_binding(continue_binding, continue_bpat); + let continue_arm = MatchArm { + pat: self.alloc_pat_desugared(Pat::TupleStruct { + path: Some(Box::new(cf_continue)), + args: Box::new([continue_bpat]), + ellipsis: None, + }), + guard: None, + expr: self.alloc_expr(Expr::Path(Path::from(continue_name)), syntax_ptr.clone()), + }; + let break_name = Name::generate_new_name(); + let break_binding = self.alloc_binding(break_name.clone(), BindingAnnotation::Unannotated); + let break_bpat = self.alloc_pat_desugared(Pat::Bind { id: break_binding, subpat: None }); + self.add_definition_to_binding(break_binding, break_bpat); + let break_arm = MatchArm { + pat: self.alloc_pat_desugared(Pat::TupleStruct { + path: Some(Box::new(cf_break)), + args: Box::new([break_bpat]), + ellipsis: None, + }), + guard: None, + expr: { + let x = self.alloc_expr(Expr::Path(Path::from(break_name)), syntax_ptr.clone()); + let callee = self.alloc_expr(Expr::Path(try_from_residual), syntax_ptr.clone()); + let result = self.alloc_expr( + Expr::Call { callee, args: Box::new([x]), is_assignee_expr: false }, + syntax_ptr.clone(), + ); + self.alloc_expr( + match self.current_try_block_label { + Some(label) => Expr::Break { expr: Some(result), label: Some(label) }, + None => Expr::Return { expr: Some(result) }, + }, + syntax_ptr.clone(), + ) + }, + }; + let arms = Box::new([continue_arm, break_arm]); + self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr) + } + fn collect_macro_call( &mut self, mcall: ast::MacroCall, @@ -616,7 +909,19 @@ fn collect_macro_call( let outer_file = self.expander.current_file_id; let macro_call_ptr = self.expander.to_source(AstPtr::new(&mcall)); - let res = self.expander.enter_expand(self.db, mcall); + let module = self.expander.module.local_id; + let res = self.expander.enter_expand(self.db, mcall, |path| { + self.def_map + .resolve_path( + self.db, + module, + &path, + crate::item_scope::BuiltinShadowMode::Other, + Some(MacroSubNs::Bang), + ) + .0 + .take_macros() + }); let res = match res { Ok(res) => res, @@ -639,7 +944,7 @@ fn collect_macro_call( krate: *krate, }); } - Some(ExpandError::RecursionOverflowPosioned) => { + Some(ExpandError::RecursionOverflowPoisoned) => { // Recursion limit has been reached in the macro expansion tree, but not in // this very macro call. Don't add diagnostics to avoid duplication. } @@ -663,7 +968,11 @@ fn collect_macro_call( self.db.ast_id_map(self.expander.current_file_id), ); - let id = collector(self, Some(expansion)); + if record_diagnostics { + // FIXME: Report parse errors here + } + + let id = collector(self, Some(expansion.tree())); self.ast_id_map = prev_ast_id_map; self.expander.exit(self.db, mark); id @@ -720,7 +1029,7 @@ fn collect_stmt(&mut self, statements: &mut Vec, s: ast::Stmt) { if self.check_cfg(&stmt).is_none() { return; } - let pat = self.collect_pat_opt(stmt.pat()); + let pat = self.collect_pat_top(stmt.pat()); let type_ref = stmt.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); let initializer = stmt.initializer().map(|e| self.collect_expr(e)); @@ -763,22 +1072,36 @@ fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { fn collect_block_( &mut self, block: ast::BlockExpr, - mk_block: impl FnOnce(BlockId, Box<[Statement]>, Option) -> Expr, + mk_block: impl FnOnce(Option, Box<[Statement]>, Option) -> Expr, ) -> ExprId { - let file_local_id = self.ast_id_map.ast_id(&block); - let ast_id = AstId::new(self.expander.current_file_id, file_local_id); - let block_loc = - BlockLoc { ast_id, module: self.expander.def_map.module_id(self.expander.module) }; - let block_id = self.db.intern_block(block_loc); - - let (module, def_map) = match self.db.block_def_map(block_id) { - Some(def_map) => { - self.body.block_scopes.push(block_id); - (def_map.root(), def_map) - } - None => (self.expander.module, self.expander.def_map.clone()), + let block_has_items = { + let statement_has_item = block.statements().any(|stmt| match stmt { + ast::Stmt::Item(_) => true, + // Macro calls can be both items and expressions. The syntax library always treats + // them as expressions here, so we undo that. + ast::Stmt::ExprStmt(es) => matches!(es.expr(), Some(ast::Expr::MacroExpr(_))), + _ => false, + }); + statement_has_item || matches!(block.tail_expr(), Some(ast::Expr::MacroExpr(_))) }; - let prev_def_map = mem::replace(&mut self.expander.def_map, def_map); + + let block_id = if block_has_items { + let file_local_id = self.ast_id_map.ast_id(&block); + let ast_id = AstId::new(self.expander.current_file_id, file_local_id); + Some(self.db.intern_block(BlockLoc { ast_id, module: self.expander.module })) + } else { + None + }; + + let (module, def_map) = + match block_id.map(|block_id| (self.db.block_def_map(block_id), block_id)) { + Some((def_map, block_id)) => { + self.body.block_scopes.push(block_id); + (def_map.module_id(DefMap::ROOT), def_map) + } + None => (self.expander.module, self.def_map.clone()), + }; + let prev_def_map = mem::replace(&mut self.def_map, def_map); let prev_local_module = mem::replace(&mut self.expander.module, module); let mut statements = Vec::new(); @@ -800,7 +1123,7 @@ fn collect_block_( let expr_id = self .alloc_expr(mk_block(block_id, statements.into_boxed_slice(), tail), syntax_node_ptr); - self.expander.def_map = prev_def_map; + self.def_map = prev_def_map; self.expander.module = prev_local_module; expr_id } @@ -812,43 +1135,46 @@ fn collect_block_opt(&mut self, expr: Option) -> ExprId { } } - fn collect_label(&mut self, ast_label: ast::Label) -> LabelId { - let label = Label { - name: ast_label.lifetime().as_ref().map_or_else(Name::missing, Name::new_lifetime), - }; - self.alloc_label(label, AstPtr::new(&ast_label)) + fn collect_labelled_block_opt( + &mut self, + label: Option, + expr: Option, + ) -> ExprId { + match label { + Some(label) => self.with_labeled_rib(label, |this| this.collect_block_opt(expr)), + None => self.collect_block_opt(expr), + } } - fn collect_pat(&mut self, pat: ast::Pat) -> PatId { - self.collect_pat_(pat, &mut BindingList::default()) - } + // region: patterns - fn collect_pat_opt(&mut self, pat: Option) -> PatId { + fn collect_pat_top(&mut self, pat: Option) -> PatId { match pat { - Some(pat) => self.collect_pat(pat), + Some(pat) => self.collect_pat(pat, &mut BindingList::default()), None => self.missing_pat(), } } - fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatId { + fn collect_pat(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatId { let pattern = match &pat { ast::Pat::IdentPat(bp) => { let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); let annotation = BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some()); - let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat, binding_list)); + let subpat = bp.pat().map(|subpat| self.collect_pat(subpat, binding_list)); let is_simple_ident_pat = annotation == BindingAnnotation::Unannotated && subpat.is_none(); let (binding, pattern) = if is_simple_ident_pat { // This could also be a single-segment path pattern. To // decide that, we need to try resolving the name. - let (resolved, _) = self.expander.def_map.resolve_path( + let (resolved, _) = self.def_map.resolve_path( self.db, - self.expander.module, + self.expander.module.local_id, &name.clone().into(), BuiltinShadowMode::Other, + None, ); match resolved.take_values() { Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())), @@ -887,11 +1213,15 @@ fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> Pat ast::Pat::TupleStructPat(p) => { let path = p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); - let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list); + let (args, ellipsis) = self.collect_tuple_pat( + p.fields(), + comma_follows_token(p.l_paren_token()), + binding_list, + ); Pat::TupleStruct { path, args, ellipsis } } ast::Pat::RefPat(p) => { - let pat = self.collect_pat_opt_(p.pat(), binding_list); + let pat = self.collect_pat_opt(p.pat(), binding_list); let mutability = Mutability::from_mutable(p.mut_token().is_some()); Pat::Ref { pat, mutability } } @@ -900,13 +1230,42 @@ fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> Pat p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); path.map(Pat::Path).unwrap_or(Pat::Missing) } - ast::Pat::OrPat(p) => { - let pats = p.pats().map(|p| self.collect_pat_(p, binding_list)).collect(); - Pat::Or(pats) + ast::Pat::OrPat(p) => 'b: { + let prev_is_used = mem::take(&mut binding_list.is_used); + let prev_reject_new = mem::take(&mut binding_list.reject_new); + let mut pats = Vec::with_capacity(p.pats().count()); + let mut it = p.pats(); + let Some(first) = it.next() else { + break 'b Pat::Or(Box::new([])); + }; + pats.push(self.collect_pat(first, binding_list)); + binding_list.reject_new = true; + for rest in it { + for (_, x) in binding_list.is_used.iter_mut() { + *x = false; + } + pats.push(self.collect_pat(rest, binding_list)); + for (&id, &x) in binding_list.is_used.iter() { + if !x { + self.body.bindings[id].problems = + Some(BindingProblems::NotBoundAcrossAll); + } + } + } + binding_list.reject_new = prev_reject_new; + let current_is_used = mem::replace(&mut binding_list.is_used, prev_is_used); + for (id, _) in current_is_used.into_iter() { + binding_list.check_is_used(self, id); + } + Pat::Or(pats.into()) } - ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat(), binding_list), + ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat(), binding_list), ast::Pat::TuplePat(p) => { - let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list); + let (args, ellipsis) = self.collect_tuple_pat( + p.fields(), + comma_follows_token(p.l_paren_token()), + binding_list, + ); Pat::Tuple { args, ellipsis } } ast::Pat::WildcardPat(_) => Pat::Wild, @@ -919,7 +1278,7 @@ fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> Pat .fields() .filter_map(|f| { let ast_pat = f.pat()?; - let pat = self.collect_pat_(ast_pat, binding_list); + let pat = self.collect_pat(ast_pat, binding_list); let name = f.field_name()?.as_name(); Some(RecordFieldPat { name, pat }) }) @@ -938,26 +1297,18 @@ fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> Pat // FIXME properly handle `RestPat` Pat::Slice { - prefix: prefix - .into_iter() - .map(|p| self.collect_pat_(p, binding_list)) - .collect(), - slice: slice.map(|p| self.collect_pat_(p, binding_list)), - suffix: suffix - .into_iter() - .map(|p| self.collect_pat_(p, binding_list)) - .collect(), + prefix: prefix.into_iter().map(|p| self.collect_pat(p, binding_list)).collect(), + slice: slice.map(|p| self.collect_pat(p, binding_list)), + suffix: suffix.into_iter().map(|p| self.collect_pat(p, binding_list)).collect(), } } - ast::Pat::LiteralPat(lit) => { - if let Some(ast_lit) = lit.literal() { - let expr = Expr::Literal(ast_lit.kind().into()); - let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit)); - let expr_id = self.alloc_expr(expr, expr_ptr); - Pat::Lit(expr_id) - } else { - Pat::Missing - } + #[rustfmt::skip] // https://github.com/rust-lang/rustfmt/issues/5676 + ast::Pat::LiteralPat(lit) => 'b: { + let Some((hir_lit, ast_lit)) = pat_literal_to_hir(lit) else { break 'b Pat::Missing }; + let expr = Expr::Literal(hir_lit); + let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit)); + let expr_id = self.alloc_expr(expr, expr_ptr); + Pat::Lit(expr_id) } ast::Pat::RestPat(_) => { // `RestPat` requires special handling and should not be mapped @@ -969,12 +1320,18 @@ fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> Pat Pat::Missing } ast::Pat::BoxPat(boxpat) => { - let inner = self.collect_pat_opt_(boxpat.pat(), binding_list); + let inner = self.collect_pat_opt(boxpat.pat(), binding_list); Pat::Box { inner } } ast::Pat::ConstBlockPat(const_block_pat) => { - if let Some(expr) = const_block_pat.block_expr() { - let expr_id = self.collect_block(expr); + if let Some(block) = const_block_pat.block_expr() { + let expr_id = self.with_label_rib(RibKind::Constant, |this| { + let syntax_ptr = AstPtr::new(&block.clone().into()); + this.collect_as_a_binding_owner_bad( + |this| this.collect_block(block), + syntax_ptr, + ) + }); Pat::ConstBlock(expr_id) } else { Pat::Missing @@ -986,23 +1343,45 @@ fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> Pat let src = self.expander.to_source(Either::Left(AstPtr::new(&pat))); let pat = self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { - this.collect_pat_opt_(expanded_pat, binding_list) + this.collect_pat_opt(expanded_pat, binding_list) }); self.source_map.pat_map.insert(src, pat); return pat; } None => Pat::Missing, }, - // FIXME: implement - ast::Pat::RangePat(_) => Pat::Missing, + // FIXME: implement in a way that also builds source map and calculates assoc resolutions in type inference. + ast::Pat::RangePat(p) => { + let mut range_part_lower = |p: Option| { + p.and_then(|x| match &x { + ast::Pat::LiteralPat(x) => { + Some(Box::new(LiteralOrConst::Literal(pat_literal_to_hir(x)?.0))) + } + ast::Pat::IdentPat(p) => { + let name = + p.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); + Some(Box::new(LiteralOrConst::Const(name.into()))) + } + ast::Pat::PathPat(p) => p + .path() + .and_then(|path| self.expander.parse_path(self.db, path)) + .map(LiteralOrConst::Const) + .map(Box::new), + _ => None, + }) + }; + let start = range_part_lower(p.start()); + let end = range_part_lower(p.end()); + Pat::Range { start, end } + } }; let ptr = AstPtr::new(&pat); self.alloc_pat(pattern, Either::Left(ptr)) } - fn collect_pat_opt_(&mut self, pat: Option, binding_list: &mut BindingList) -> PatId { + fn collect_pat_opt(&mut self, pat: Option, binding_list: &mut BindingList) -> PatId { match pat { - Some(pat) => self.collect_pat_(pat, binding_list), + Some(pat) => self.collect_pat(pat, binding_list), None => self.missing_pat(), } } @@ -1010,20 +1389,28 @@ fn collect_pat_opt_(&mut self, pat: Option, binding_list: &mut Binding fn collect_tuple_pat( &mut self, args: AstChildren, + has_leading_comma: bool, binding_list: &mut BindingList, ) -> (Box<[PatId]>, Option) { // Find the location of the `..`, if there is one. Note that we do not // consider the possibility of there being multiple `..` here. let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_))); // We want to skip the `..` pattern here, since we account for it above. - let args = args + let mut args: Vec<_> = args .filter(|p| !matches!(p, ast::Pat::RestPat(_))) - .map(|p| self.collect_pat_(p, binding_list)) + .map(|p| self.collect_pat(p, binding_list)) .collect(); + // if there is a leading comma, the user is most likely to type out a leading pattern + // so we insert a missing pattern at the beginning for IDE features + if has_leading_comma { + args.insert(0, self.missing_pat()); + } - (args, ellipsis) + (args.into_boxed_slice(), ellipsis) } + // endregion: patterns + /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when /// not. fn check_cfg(&mut self, owner: &dyn ast::HasAttrs) -> Option<()> { @@ -1051,42 +1438,147 @@ fn check_cfg(&mut self, owner: &dyn ast::HasAttrs) -> Option<()> { fn add_definition_to_binding(&mut self, binding_id: BindingId, pat_id: PatId) { self.body.bindings[binding_id].definitions.push(pat_id); } -} -impl From for Literal { - fn from(ast_lit_kind: ast::LiteralKind) -> Self { - match ast_lit_kind { - // FIXME: these should have actual values filled in, but unsure on perf impact - LiteralKind::IntNumber(lit) => { - if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { - Literal::Float( - FloatTypeWrapper::new(lit.float_value().unwrap_or(Default::default())), - builtin, - ) - } else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinInt::from_suffix) { - Literal::Int(lit.value().unwrap_or(0) as i128, builtin) - } else { - let builtin = lit.suffix().and_then(BuiltinUint::from_suffix); - Literal::Uint(lit.value().unwrap_or(0), builtin) + // region: labels + + fn collect_label(&mut self, ast_label: ast::Label) -> LabelId { + let label = Label { + name: ast_label.lifetime().as_ref().map_or_else(Name::missing, Name::new_lifetime), + }; + self.alloc_label(label, AstPtr::new(&ast_label)) + } + + fn resolve_label( + &self, + lifetime: Option, + ) -> Result, BodyDiagnostic> { + let Some(lifetime) = lifetime else { + return Ok(None) + }; + let name = Name::new_lifetime(&lifetime); + + for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() { + if let Some((label_name, id)) = &rib.label { + if *label_name == name { + return if self.is_label_valid_from_rib(rib_idx) { + Ok(Some(*id)) + } else { + Err(BodyDiagnostic::UnreachableLabel { + name, + node: InFile::new( + self.expander.current_file_id, + AstPtr::new(&lifetime), + ), + }) + }; } } - LiteralKind::FloatNumber(lit) => { - let ty = lit.suffix().and_then(BuiltinFloat::from_suffix); - Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty) - } - LiteralKind::ByteString(bs) => { - let text = bs.value().map(Box::from).unwrap_or_else(Default::default); - Literal::ByteString(text) - } - LiteralKind::String(s) => { - let text = s.value().map(Box::from).unwrap_or_else(Default::default); - Literal::String(text) - } - LiteralKind::Byte(b) => { - Literal::Uint(b.value().unwrap_or_default() as u128, Some(BuiltinUint::U8)) - } - LiteralKind::Char(c) => Literal::Char(c.value().unwrap_or_default()), - LiteralKind::Bool(val) => Literal::Bool(val), + } + + Err(BodyDiagnostic::UndeclaredLabel { + name, + node: InFile::new(self.expander.current_file_id, AstPtr::new(&lifetime)), + }) + } + + fn is_label_valid_from_rib(&self, rib_index: usize) -> bool { + !self.label_ribs[rib_index + 1..].iter().any(|rib| rib.kind.is_label_barrier()) + } + + fn with_label_rib(&mut self, kind: RibKind, f: impl FnOnce(&mut Self) -> T) -> T { + self.label_ribs.push(LabelRib::new(kind)); + let res = f(self); + self.label_ribs.pop(); + res + } + + fn with_labeled_rib(&mut self, label: LabelId, f: impl FnOnce(&mut Self) -> T) -> T { + self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label))); + let res = f(self); + self.label_ribs.pop(); + res + } + + fn with_opt_labeled_rib( + &mut self, + label: Option, + f: impl FnOnce(&mut Self) -> T, + ) -> T { + match label { + None => f(self), + Some(label) => self.with_labeled_rib(label, f), } } + // endregion: labels +} + +fn pat_literal_to_hir(lit: &ast::LiteralPat) -> Option<(Literal, ast::Literal)> { + let ast_lit = lit.literal()?; + let mut hir_lit: Literal = ast_lit.kind().into(); + if lit.minus_token().is_some() { + let Some(h) = hir_lit.negate() else { + return None; + }; + hir_lit = h; + } + Some((hir_lit, ast_lit)) +} + +impl ExprCollector<'_> { + fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { + let src = self.expander.to_source(ptr); + let id = self.body.exprs.alloc(expr); + self.source_map.expr_map_back.insert(id, src.clone()); + self.source_map.expr_map.insert(src, id); + id + } + // FIXME: desugared exprs don't have ptr, that's wrong and should be fixed somehow. + fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { + self.body.exprs.alloc(expr) + } + fn missing_expr(&mut self) -> ExprId { + self.alloc_expr_desugared(Expr::Missing) + } + + fn alloc_binding(&mut self, name: Name, mode: BindingAnnotation) -> BindingId { + self.body.bindings.alloc(Binding { + name, + mode, + definitions: SmallVec::new(), + owner: self.current_binding_owner, + problems: None, + }) + } + + fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { + let src = self.expander.to_source(ptr); + let id = self.body.pats.alloc(pat); + self.source_map.pat_map_back.insert(id, src.clone()); + self.source_map.pat_map.insert(src, id); + id + } + // FIXME: desugared pats don't have ptr, that's wrong and should be fixed somehow. + fn alloc_pat_desugared(&mut self, pat: Pat) -> PatId { + self.body.pats.alloc(pat) + } + fn missing_pat(&mut self) -> PatId { + self.body.pats.alloc(Pat::Missing) + } + + fn alloc_label(&mut self, label: Label, ptr: LabelPtr) -> LabelId { + let src = self.expander.to_source(ptr); + let id = self.body.labels.alloc(label); + self.source_map.label_map_back.insert(id, src.clone()); + self.source_map.label_map.insert(src, id); + id + } + // FIXME: desugared labels don't have ptr, that's wrong and should be fixed somehow. + fn alloc_label_desugared(&mut self, label: Label) -> LabelId { + self.body.labels.alloc(label) + } +} + +fn comma_follows_token(t: Option) -> bool { + (|| syntax::algo::skip_trivia_token(t?.next_token()?, syntax::Direction::Next))() + .map_or(false, |it| it.kind() == syntax::T![,]) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs index 5a9b825a2530..88380aa355d1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs @@ -2,10 +2,14 @@ use std::fmt::{self, Write}; +use hir_expand::db::ExpandDatabase; use syntax::ast::HasName; use crate::{ - expr::{Array, BindingAnnotation, BindingId, ClosureKind, Literal, Movability, Statement}, + hir::{ + Array, BindingAnnotation, BindingId, CaptureBy, ClosureKind, Literal, LiteralOrConst, + Movability, Statement, + }, pretty::{print_generic_args, print_path, print_type_ref}, type_ref::TypeRef, }; @@ -13,47 +17,70 @@ use super::*; pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBodyId) -> String { - let needs_semi; let header = match owner { DefWithBodyId::FunctionId(it) => { - needs_semi = false; let item_tree_id = it.lookup(db).id; - format!("fn {}(…) ", item_tree_id.item_tree(db)[item_tree_id.value].name) + format!( + "fn {}", + item_tree_id.item_tree(db)[item_tree_id.value].name.display(db.upcast()) + ) } DefWithBodyId::StaticId(it) => { - needs_semi = true; let item_tree_id = it.lookup(db).id; - format!("static {} = ", item_tree_id.item_tree(db)[item_tree_id.value].name) + format!( + "static {} = ", + item_tree_id.item_tree(db)[item_tree_id.value].name.display(db.upcast()) + ) } DefWithBodyId::ConstId(it) => { - needs_semi = true; let item_tree_id = it.lookup(db).id; let name = match &item_tree_id.item_tree(db)[item_tree_id.value].name { - Some(name) => name.to_string(), + Some(name) => name.display(db.upcast()).to_string(), None => "_".to_string(), }; 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() { + 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 }; + let mut p = + Printer { db: db.upcast(), body, buf: header, indent_level: 0, needs_indent: false }; + if let DefWithBodyId::FunctionId(it) = owner { + p.buf.push('('); + body.params.iter().zip(&db.function_data(it).params).for_each(|(¶m, ty)| { + p.print_pat(param); + p.buf.push(':'); + p.print_type_ref(ty); + }); + p.buf.push(')'); + p.buf.push(' '); + } p.print_expr(body.body_expr); - if needs_semi { + if matches!(owner, DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_)) { p.buf.push(';'); } p.buf } +pub(super) fn print_expr_hir( + db: &dyn DefDatabase, + body: &Body, + _owner: DefWithBodyId, + expr: ExprId, +) -> String { + let mut p = + Printer { db: db.upcast(), body, buf: String::new(), indent_level: 0, needs_indent: false }; + p.print_expr(expr); + p.buf +} + macro_rules! w { ($dst:expr, $($arg:tt)*) => { { let _ = write!($dst, $($arg)*); } @@ -70,6 +97,7 @@ macro_rules! wln { } struct Printer<'a> { + db: &'a dyn ExpandDatabase, body: &'a Body, buf: String, indent_level: usize, @@ -144,29 +172,19 @@ fn print_expr(&mut self, expr: ExprId) { } Expr::Loop { body, label } => { if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name); + w!(self, "{}: ", self.body[*lbl].name.display(self.db)); } w!(self, "loop "); self.print_expr(*body); } Expr::While { condition, body, label } => { if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name); + w!(self, "{}: ", self.body[*lbl].name.display(self.db)); } w!(self, "while "); self.print_expr(*condition); self.print_expr(*body); } - Expr::For { iterable, pat, body, label } => { - if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name); - } - w!(self, "for "); - self.print_pat(*pat); - w!(self, " in "); - self.print_expr(*iterable); - self.print_expr(*body); - } Expr::Call { callee, args, is_assignee_expr: _ } => { self.print_expr(*callee); w!(self, "("); @@ -182,10 +200,10 @@ fn print_expr(&mut self, expr: ExprId) { } Expr::MethodCall { receiver, method_name, args, generic_args } => { self.print_expr(*receiver); - w!(self, ".{}", method_name); + w!(self, ".{}", method_name.display(self.db)); if let Some(args) = generic_args { w!(self, "::<"); - print_generic_args(args, self).unwrap(); + print_generic_args(self.db, args, self).unwrap(); w!(self, ">"); } w!(self, "("); @@ -219,14 +237,14 @@ fn print_expr(&mut self, expr: ExprId) { } Expr::Continue { label } => { w!(self, "continue"); - if let Some(label) = label { - w!(self, " {}", label); + if let Some(lbl) = label { + w!(self, " {}", self.body[*lbl].name.display(self.db)); } } Expr::Break { expr, label } => { w!(self, "break"); - if let Some(label) = label { - w!(self, " {}", label); + if let Some(lbl) = label { + w!(self, " {}", self.body[*lbl].name.display(self.db)); } if let Some(expr) = expr { self.whitespace(); @@ -265,7 +283,7 @@ fn print_expr(&mut self, expr: ExprId) { w!(self, "{{"); self.indented(|p| { for field in &**fields { - w!(p, "{}: ", field.name); + w!(p, "{}: ", field.name.display(self.db)); p.print_expr(field.expr); wln!(p, ","); } @@ -282,16 +300,12 @@ fn print_expr(&mut self, expr: ExprId) { } Expr::Field { expr, name } => { self.print_expr(*expr); - w!(self, ".{}", name); + w!(self, ".{}", name.display(self.db)); } Expr::Await { expr } => { self.print_expr(*expr); w!(self, ".await"); } - Expr::Try { expr } => { - self.print_expr(*expr); - w!(self, "?"); - } Expr::Cast { expr, type_ref } => { self.print_expr(*expr); w!(self, " as "); @@ -359,7 +373,7 @@ fn print_expr(&mut self, expr: ExprId) { self.print_expr(*index); w!(self, "]"); } - Expr::Closure { args, arg_types, ret_type, body, closure_kind } => { + Expr::Closure { args, arg_types, ret_type, body, closure_kind, capture_by } => { match closure_kind { ClosureKind::Generator(Movability::Static) => { w!(self, "static "); @@ -369,6 +383,12 @@ fn print_expr(&mut self, expr: ExprId) { } _ => (), } + match capture_by { + CaptureBy::Value => { + w!(self, "move "); + } + CaptureBy::Ref => (), + } w!(self, "|"); for (i, (pat, ty)) in args.iter().zip(arg_types.iter()).enumerate() { if i != 0 { @@ -418,20 +438,17 @@ fn print_expr(&mut self, expr: ExprId) { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - let label = label.map(|lbl| format!("{}: ", self.body[lbl].name)); + let label = label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db))); self.print_block(label.as_deref(), statements, tail); } Expr::Unsafe { id: _, statements, tail } => { self.print_block(Some("unsafe "), statements, tail); } - Expr::TryBlock { id: _, statements, tail } => { - self.print_block(Some("try "), statements, tail); - } Expr::Async { id: _, statements, tail } => { self.print_block(Some("async "), statements, tail); } - Expr::Const { id: _, statements, tail } => { - self.print_block(Some("const "), statements, tail); + Expr::Const(id) => { + w!(self, "const {{ /* {id:?} */ }}"); } } } @@ -439,7 +456,7 @@ fn print_expr(&mut self, expr: ExprId) { fn print_block( &mut self, label: Option<&str>, - statements: &Box<[Statement]>, + statements: &[Statement], tail: &Option>, ) { self.whitespace(); @@ -449,7 +466,7 @@ fn print_block( w!(self, "{{"); if !statements.is_empty() || tail.is_some() { self.indented(|p| { - for stmt in &**statements { + for stmt in statements { p.print_stmt(stmt); } if let Some(tail) = tail { @@ -497,7 +514,7 @@ fn print_pat(&mut self, pat: PatId) { w!(self, " {{"); self.indented(|p| { for arg in args.iter() { - w!(p, "{}: ", arg.name); + w!(p, "{}: ", arg.name.display(self.db)); p.print_pat(arg.pat); wln!(p, ","); } @@ -508,9 +525,13 @@ fn print_pat(&mut self, pat: PatId) { w!(self, "}}"); } Pat::Range { start, end } => { - self.print_expr(*start); - w!(self, "..."); - self.print_expr(*end); + if let Some(start) = start { + self.print_literal_or_const(start); + } + w!(self, "..="); + if let Some(end) = end { + self.print_literal_or_const(end); + } } Pat::Slice { prefix, slice, suffix } => { w!(self, "["); @@ -601,10 +622,18 @@ fn print_stmt(&mut self, stmt: &Statement) { } } + fn print_literal_or_const(&mut self, literal_or_const: &LiteralOrConst) { + match literal_or_const { + LiteralOrConst::Literal(l) => self.print_literal(l), + LiteralOrConst::Const(c) => self.print_path(c), + } + } + fn print_literal(&mut self, literal: &Literal) { match literal { Literal::String(it) => w!(self, "{:?}", it), Literal::ByteString(it) => w!(self, "\"{}\"", it.escape_ascii()), + Literal::CString(it) => w!(self, "\"{}\\0\"", it), Literal::Char(it) => w!(self, "'{}'", it.escape_debug()), Literal::Bool(it) => w!(self, "{}", it), Literal::Int(i, suffix) => { @@ -629,11 +658,11 @@ fn print_literal(&mut self, literal: &Literal) { } fn print_type_ref(&mut self, ty: &TypeRef) { - print_type_ref(ty, self).unwrap(); + print_type_ref(self.db, ty, self).unwrap(); } fn print_path(&mut self, path: &Path) { - print_path(path, self).unwrap(); + print_path(self.db, path, self).unwrap(); } fn print_binding(&mut self, id: BindingId) { @@ -644,6 +673,6 @@ fn print_binding(&mut self, id: BindingId) { BindingAnnotation::Ref => "ref ", BindingAnnotation::RefMut => "ref mut ", }; - w!(self, "{}{}", mode, name); + w!(self, "{}{}", mode, name.display(self.db)); } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs index 12fc1f116d7d..69741c445fba 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs @@ -1,14 +1,13 @@ //! Name resolution for expressions. -use std::sync::Arc; - use hir_expand::name::Name; -use la_arena::{Arena, Idx}; +use la_arena::{Arena, Idx, IdxRange, RawIdx}; use rustc_hash::FxHashMap; +use triomphe::Arc; use crate::{ body::Body, db::DefDatabase, - expr::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement}, + hir::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement}, BlockId, DefWithBodyId, }; @@ -17,6 +16,7 @@ #[derive(Debug, PartialEq, Eq)] pub struct ExprScopes { scopes: Arena, + scope_entries: Arena, scope_by_expr: FxHashMap, } @@ -41,7 +41,7 @@ pub struct ScopeData { parent: Option, block: Option, label: Option<(LabelId, Name)>, - entries: Vec, + entries: IdxRange, } impl ExprScopes { @@ -53,7 +53,7 @@ pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc } pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { - &self.scopes[scope].entries + &self.scope_entries[self.scopes[scope].entries.clone()] } /// If `scope` refers to a block expression scope, returns the corresponding `BlockId`. @@ -85,10 +85,17 @@ pub fn scope_by_expr(&self) -> &FxHashMap { } } +fn empty_entries(idx: usize) -> IdxRange { + IdxRange::new(Idx::from_raw(RawIdx::from(idx as u32))..Idx::from_raw(RawIdx::from(idx as u32))) +} + impl ExprScopes { fn new(body: &Body) -> ExprScopes { - let mut scopes = - ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() }; + let mut scopes = ExprScopes { + scopes: Arena::default(), + scope_entries: Arena::default(), + scope_by_expr: FxHashMap::default(), + }; let mut root = scopes.root_scope(); scopes.add_params_bindings(body, root, &body.params); compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root); @@ -96,7 +103,12 @@ fn new(body: &Body) -> ExprScopes { } fn root_scope(&mut self) -> ScopeId { - self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] }) + self.scopes.alloc(ScopeData { + parent: None, + block: None, + label: None, + entries: empty_entries(self.scope_entries.len()), + }) } fn new_scope(&mut self, parent: ScopeId) -> ScopeId { @@ -104,32 +116,38 @@ fn new_scope(&mut self, parent: ScopeId) -> ScopeId { parent: Some(parent), block: None, label: None, - entries: vec![], + entries: empty_entries(self.scope_entries.len()), }) } fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId { - self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] }) + self.scopes.alloc(ScopeData { + parent: Some(parent), + block: None, + label, + entries: empty_entries(self.scope_entries.len()), + }) } fn new_block_scope( &mut self, parent: ScopeId, - block: BlockId, + block: Option, label: Option<(LabelId, Name)>, ) -> ScopeId { self.scopes.alloc(ScopeData { parent: Some(parent), - block: Some(block), + block, label, - entries: vec![], + entries: empty_entries(self.scope_entries.len()), }) } fn add_bindings(&mut self, body: &Body, scope: ScopeId, binding: BindingId) { let Binding { name, .. } = &body.bindings[binding]; - let entry = ScopeEntry { name: name.clone(), binding }; - self.scopes[scope].entries.push(entry); + let entry = self.scope_entries.alloc(ScopeEntry { name: name.clone(), binding }); + self.scopes[scope].entries = + IdxRange::new_inclusive(self.scopes[scope].entries.start()..=entry); } fn add_pat_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { @@ -150,9 +168,9 @@ fn set_scope(&mut self, node: ExprId, scope: ScopeId) { } fn shrink_to_fit(&mut self) { - let ExprScopes { scopes, scope_by_expr } = self; + let ExprScopes { scopes, scope_entries, scope_by_expr } = self; scopes.shrink_to_fit(); - scopes.values_mut().for_each(|it| it.entries.shrink_to_fit()); + scope_entries.shrink_to_fit(); scope_by_expr.shrink_to_fit(); } } @@ -200,22 +218,16 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope scopes.set_scope(expr, scope); compute_block_scopes(statements, *tail, body, scopes, &mut scope); } - Expr::Unsafe { id, statements, tail } - | Expr::Async { id, statements, tail } - | Expr::Const { id, statements, tail } - | Expr::TryBlock { id, statements, tail } => { + Expr::Const(_) => { + // FIXME: This is broken. + } + Expr::Unsafe { id, statements, tail } | Expr::Async { id, statements, tail } => { let mut scope = scopes.new_block_scope(*scope, *id, None); // Overwrite the old scope for the block expr, so that every block scope can be found // via the block itself (important for blocks that only contain items, no expressions). scopes.set_scope(expr, scope); compute_block_scopes(statements, *tail, body, scopes, &mut scope); } - Expr::For { iterable, pat, body: body_expr, label } => { - compute_expr_scopes(*iterable, body, scopes, scope); - let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); - scopes.add_pat_bindings(body, scope, *pat); - compute_expr_scopes(*body_expr, body, scopes, &mut scope); - } Expr::While { condition, body: body_expr, label } => { let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); compute_expr_scopes(*condition, body, scopes, &mut scope); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs index 77ac221e590c..6e77744f2153 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs @@ -148,8 +148,8 @@ fn f() { } "#, expect![[r#" - BlockId(1) in ModuleId { krate: CrateId(0), block: Some(BlockId(0)), local_id: Idx::(1) } - BlockId(0) in ModuleId { krate: CrateId(0), block: None, local_id: Idx::(0) } + BlockId(1) in BlockRelativeModuleId { block: Some(BlockId(0)), local_id: Idx::(1) } + BlockId(0) in BlockRelativeModuleId { block: None, local_id: Idx::(0) } crate scope "#]], ); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs b/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs index dd69c3ab4731..61b2481978e2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs @@ -106,8 +106,14 @@ fn as_name(&self) -> Name { impl fmt::Display for BuiltinType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let type_name = self.as_name(); - type_name.fmt(f) + match self { + BuiltinType::Char => f.write_str("char"), + BuiltinType::Bool => f.write_str("bool"), + BuiltinType::Str => f.write_str("str"), + BuiltinType::Int(it) => it.fmt(f), + BuiltinType::Uint(it) => it.fmt(f), + BuiltinType::Float(it) => it.fmt(f), + } } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs index 68b57acca2ad..bb79e28f2673 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs @@ -10,9 +10,9 @@ use crate::{ db::DefDatabase, - dyn_map::DynMap, + dyn_map::{keys, DynMap}, item_scope::ItemScope, - keys, + nameres::DefMap, src::{HasChildSource, HasSource}, AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, FieldId, ImplId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, VariantId, @@ -206,7 +206,7 @@ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: Hi 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`. - def_map[def_map.root()].scope.child_by_source_to(db, res, file_id); + def_map[DefMap::ROOT].scope.child_by_source_to(db, res, file_id); } } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index 1633a33bedde..40e6a4308784 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -1,22 +1,28 @@ //! Contains basic data about various HIR declarations. -use std::sync::Arc; +pub mod adt; -use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroDefKind}; +use hir_expand::{ + name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefKind, +}; use intern::Interned; use smallvec::SmallVec; -use syntax::ast; +use syntax::{ast, Parse}; +use triomphe::Arc; use crate::{ attr::Attrs, - body::{Expander, Mark}, db::DefDatabase, - item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId}, + expander::{Expander, Mark}, + item_tree::{ + self, AssocItem, FnFlags, ItemTree, ItemTreeId, MacroCall, ModItem, Param, TreeId, + }, + macro_call_as_call_id, macro_id_to_def_id, nameres::{ attr_resolution::ResolvedAttr, diagnostics::DefDiagnostic, proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroKind}, - DefMap, + DefMap, MacroSubNs, }, type_ref::{TraitRef, TypeBound, TypeRef}, visibility::RawVisibility, @@ -28,9 +34,8 @@ #[derive(Debug, Clone, PartialEq, Eq)] pub struct FunctionData { pub name: Name, - pub params: Vec<(Option, Interned)>, + pub params: Vec>, pub ret_type: Interned, - pub async_ret_type: Option>, pub attrs: Attrs, pub visibility: RawVisibility, pub abi: Option>, @@ -43,16 +48,16 @@ impl FunctionData { pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc { let loc = func.lookup(db); let krate = loc.container.module(db).krate; - let crate_graph = db.crate_graph(); - let cfg_options = &crate_graph[krate].cfg_options; let item_tree = loc.id.item_tree(db); let func = &item_tree[loc.id.value]; let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container { - db.trait_data(trait_id).visibility.clone() + trait_vis(db, trait_id) } else { item_tree[func.visibility].clone() }; + let crate_graph = db.crate_graph(); + let cfg_options = &crate_graph[krate].cfg_options; let enabled_params = func .params .clone() @@ -99,12 +104,11 @@ pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc Some((name.clone(), ty.clone())), + Param::Normal(ty) => Some(ty.clone()), Param::Varargs => None, }) .collect(), ret_type: func.ret_type.clone(), - async_ret_type: func.async_ret_type.clone(), attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()), visibility, abi: func.abi.clone(), @@ -188,7 +192,7 @@ pub(crate) fn type_alias_data_query( let item_tree = loc.id.item_tree(db); let typ = &item_tree[loc.id.value]; let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container { - db.trait_data(trait_id).visibility.clone() + trait_vis(db, trait_id) } else { item_tree[typ.visibility].clone() }; @@ -471,7 +475,7 @@ pub(crate) fn const_data_query(db: &dyn DefDatabase, konst: ConstId) -> Arc { db: &'a dyn DefDatabase, module_id: ModuleId, def_map: Arc, - inactive_diagnostics: Vec, + diagnostics: Vec, container: ItemContainerId, expander: Expander, @@ -542,7 +546,7 @@ fn new( expander: Expander::new(db, file_id, module_id), items: Vec::new(), attr_calls: Vec::new(), - inactive_diagnostics: Vec::new(), + diagnostics: Vec::new(), } } @@ -556,11 +560,10 @@ fn finish( ( self.items, if self.attr_calls.is_empty() { None } else { Some(Box::new(self.attr_calls)) }, - self.inactive_diagnostics, + self.diagnostics, ) } - // FIXME: proc-macro diagnostics fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[AssocItem]) { let container = self.container; self.items.reserve(assoc_items.len()); @@ -568,7 +571,7 @@ fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[Asso 'items: for &item in assoc_items { let attrs = item_tree.attrs(self.db, self.module_id.krate, ModItem::from(item).into()); if !attrs.is_cfg_enabled(self.expander.cfg_options()) { - self.inactive_diagnostics.push(DefDiagnostic::unconfigured_code( + self.diagnostics.push(DefDiagnostic::unconfigured_code( self.module_id.local_id, InFile::new(self.expander.current_file_id(), item.ast_id(item_tree).upcast()), attrs.cfg().unwrap(), @@ -582,84 +585,164 @@ fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[Asso AstId::new(self.expander.current_file_id(), item.ast_id(item_tree).upcast()); let ast_id_with_path = AstIdWithPath { path: (*attr.path).clone(), ast_id }; - if let Ok(ResolvedAttr::Macro(call_id)) = self.def_map.resolve_attr_macro( + match self.def_map.resolve_attr_macro( self.db, self.module_id.local_id, ast_id_with_path, attr, ) { - self.attr_calls.push((ast_id, call_id)); - // If proc attribute macro expansion is disabled, skip expanding it here - if !self.db.enable_proc_attr_macros() { - continue 'attrs; - } - let loc = self.db.lookup_intern_macro_call(call_id); - if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind { - // If there's no expander for the proc macro (e.g. the - // proc macro is ignored, or building the proc macro - // crate failed), skip expansion like we would if it was - // disabled. This is analogous to the handling in - // `DefCollector::collect_macros`. - if exp.is_dummy() { + Ok(ResolvedAttr::Macro(call_id)) => { + self.attr_calls.push((ast_id, call_id)); + // If proc attribute macro expansion is disabled, skip expanding it here + if !self.db.expand_proc_attr_macros() { continue 'attrs; } - } - match self.expander.enter_expand_id::(self.db, call_id) { - ExpandResult { value: Some((mark, _)), .. } => { - self.collect_macro_items(mark); - continue 'items; + let loc = self.db.lookup_intern_macro_call(call_id); + if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind { + // If there's no expander for the proc macro (e.g. the + // proc macro is ignored, or building the proc macro + // crate failed), skip expansion like we would if it was + // disabled. This is analogous to the handling in + // `DefCollector::collect_macros`. + if exp.is_dummy() { + continue 'attrs; + } } - ExpandResult { .. } => {} + + let res = + self.expander.enter_expand_id::(self.db, call_id); + self.collect_macro_items(res, &|| loc.kind.clone()); + continue 'items; + } + Ok(_) => (), + Err(_) => { + self.diagnostics.push(DefDiagnostic::unresolved_macro_call( + self.module_id.local_id, + MacroCallKind::Attr { + ast_id, + attr_args: Arc::new((tt::Subtree::empty(), Default::default())), + invoc_attr_index: attr.id, + }, + attr.path().clone(), + )); } } } - match item { - AssocItem::Function(id) => { - let item = &item_tree[id]; + self.collect_item(item_tree, tree_id, container, item); + } + } - let def = - FunctionLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); - self.items.push((item.name.clone(), def.into())); - } - AssocItem::Const(id) => { - let item = &item_tree[id]; + fn collect_item( + &mut self, + item_tree: &ItemTree, + tree_id: TreeId, + container: ItemContainerId, + item: AssocItem, + ) { + match item { + AssocItem::Function(id) => { + let item = &item_tree[id]; - let name = match item.name.clone() { - Some(name) => name, - None => continue, - }; - let def = - ConstLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); - self.items.push((name, def.into())); - } - AssocItem::TypeAlias(id) => { - let item = &item_tree[id]; + let def = + FunctionLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((item.name.clone(), def.into())); + } + AssocItem::Const(id) => { + let item = &item_tree[id]; + let Some(name) = item.name.clone() else { return }; + let def = ConstLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((name, def.into())); + } + AssocItem::TypeAlias(id) => { + let item = &item_tree[id]; - let def = TypeAliasLoc { container, id: ItemTreeId::new(tree_id, id) } - .intern(self.db); - self.items.push((item.name.clone(), def.into())); - } - AssocItem::MacroCall(call) => { - if let Some(root) = self.db.parse_or_expand(self.expander.current_file_id()) { - let call = &item_tree[call]; + let def = + TypeAliasLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((item.name.clone(), def.into())); + } + AssocItem::MacroCall(call) => { + let file_id = self.expander.current_file_id(); + let MacroCall { ast_id, expand_to, ref path } = item_tree[call]; + let module = self.expander.module.local_id; - let ast_id_map = self.db.ast_id_map(self.expander.current_file_id()); - let call = ast_id_map.get(call.ast_id).to_node(&root); - let _cx = - stdx::panic_context::enter(format!("collect_items MacroCall: {call}")); - let res = self.expander.enter_expand::(self.db, call); - - if let Ok(ExpandResult { value: Some((mark, _)), .. }) = res { - self.collect_macro_items(mark); - } + let resolver = |path| { + self.def_map + .resolve_path( + self.db, + module, + &path, + crate::item_scope::BuiltinShadowMode::Other, + Some(MacroSubNs::Bang), + ) + .0 + .take_macros() + .map(|it| macro_id_to_def_id(self.db, it)) + }; + match macro_call_as_call_id( + self.db.upcast(), + &AstIdWithPath::new(file_id, ast_id, Clone::clone(path)), + expand_to, + self.expander.module.krate(), + resolver, + ) { + Ok(Some(call_id)) => { + let res = + self.expander.enter_expand_id::(self.db, call_id); + self.collect_macro_items(res, &|| hir_expand::MacroCallKind::FnLike { + ast_id: InFile::new(file_id, ast_id), + expand_to: hir_expand::ExpandTo::Items, + }); + } + Ok(None) => (), + Err(_) => { + self.diagnostics.push(DefDiagnostic::unresolved_macro_call( + self.module_id.local_id, + MacroCallKind::FnLike { + ast_id: InFile::new(file_id, ast_id), + expand_to, + }, + Clone::clone(path), + )); } } } } } - fn collect_macro_items(&mut self, mark: Mark) { + fn collect_macro_items( + &mut self, + ExpandResult { value, err }: ExpandResult)>>, + error_call_kind: &dyn Fn() -> hir_expand::MacroCallKind, + ) { + let Some((mark, parse)) = value else { return }; + + if let Some(err) = err { + let diag = match err { + // why is this reported here? + hir_expand::ExpandError::UnresolvedProcMacro(krate) => { + DefDiagnostic::unresolved_proc_macro( + self.module_id.local_id, + error_call_kind(), + krate, + ) + } + _ => DefDiagnostic::macro_error( + self.module_id.local_id, + error_call_kind(), + err.to_string(), + ), + }; + self.diagnostics.push(diag); + } + if let errors @ [_, ..] = parse.errors() { + self.diagnostics.push(DefDiagnostic::macro_expansion_parse_error( + self.module_id.local_id, + error_call_kind(), + errors.into(), + )); + } + let tree_id = item_tree::TreeId::new(self.expander.current_file_id(), None); let item_tree = tree_id.item_tree(self.db); let iter: SmallVec<[_; 2]> = @@ -670,3 +753,10 @@ fn collect_macro_items(&mut self, mark: Mark) { self.expander.exit(self.db, mark); } } + +fn trait_vis(db: &dyn DefDatabase, trait_id: TraitId) -> RawVisibility { + let ItemLoc { id: tree_id, .. } = trait_id.lookup(db); + let item_tree = tree_id.item_tree(db); + let tr_def = &item_tree[tree_id.value]; + item_tree[tr_def.visibility].clone() +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs similarity index 89% rename from src/tools/rust-analyzer/crates/hir-def/src/adt.rs rename to src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs index b336f59ffee3..6db5abccc921 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs @@ -1,8 +1,7 @@ //! Defines hir-level representation of structs, enums and unions -use std::sync::Arc; - use base_db::CrateId; +use bitflags::bitflags; use cfg::CfgOptions; use either::Either; @@ -12,15 +11,17 @@ }; use intern::Interned; use la_arena::{Arena, ArenaMap}; -use rustc_abi::{Integer, IntegerType}; +use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions}; use syntax::ast::{self, HasName, HasVisibility}; +use triomphe::Arc; use crate::{ - body::{CfgExpander, LowerCtx}, builtin_type::{BuiltinInt, BuiltinUint}, db::DefDatabase, + expander::CfgExpander, item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId}, - layout::{Align, ReprFlags, ReprOptions}, + lang_item::LangItem, + lower::LowerCtx, nameres::diagnostics::DefDiagnostic, src::HasChildSource, src::HasSource, @@ -39,8 +40,27 @@ pub struct StructData { pub variant_data: Arc, pub repr: Option, pub visibility: RawVisibility, - pub rustc_has_incoherent_inherent_impls: bool, - pub fundamental: bool, + pub flags: StructFlags, +} + +bitflags! { +#[derive(Debug, Clone, PartialEq, Eq)] + pub struct StructFlags: u8 { + const NO_FLAGS = 0; + /// Indicates whether the struct is `PhantomData`. + const IS_PHANTOM_DATA = 1 << 2; + /// Indicates whether the struct has a `#[fundamental]` attribute. + const IS_FUNDAMENTAL = 1 << 3; + // FIXME: should this be a flag? + /// Indicates whether the struct has a `#[rustc_has_incoherent_inherent_impls]` attribute. + const IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL = 1 << 4; + /// Indicates whether this struct is `Box`. + const IS_BOX = 1 << 5; + /// Indicates whether this struct is `ManuallyDrop`. + const IS_MANUALLY_DROP = 1 << 6; + /// Indicates whether this struct is `UnsafeCell`. + const IS_UNSAFE_CELL = 1 << 7; + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -174,10 +194,25 @@ pub(crate) fn struct_data_with_diagnostics_query( let item_tree = loc.id.item_tree(db); let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); + let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); - let rustc_has_incoherent_inherent_impls = - attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); - let fundamental = attrs.by_key("fundamental").exists(); + + let mut flags = StructFlags::NO_FLAGS; + if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() { + flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL; + } + if attrs.by_key("fundamental").exists() { + flags |= StructFlags::IS_FUNDAMENTAL; + } + if let Some(lang) = attrs.lang_item() { + match lang { + LangItem::PhantomData => flags |= StructFlags::IS_PHANTOM_DATA, + LangItem::OwnedBox => flags |= StructFlags::IS_BOX, + LangItem::ManuallyDrop => flags |= StructFlags::IS_MANUALLY_DROP, + LangItem::UnsafeCell => flags |= StructFlags::IS_UNSAFE_CELL, + _ => (), + } + } let strukt = &item_tree[loc.id.value]; let (variant_data, diagnostics) = lower_fields( @@ -196,8 +231,7 @@ pub(crate) fn struct_data_with_diagnostics_query( variant_data: Arc::new(variant_data), repr, visibility: item_tree[strukt.visibility].clone(), - rustc_has_incoherent_inherent_impls, - fundamental, + flags, }), diagnostics.into(), ) @@ -218,9 +252,13 @@ pub(crate) fn union_data_with_diagnostics_query( let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); - let rustc_has_incoherent_inherent_impls = - attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); - let fundamental = attrs.by_key("fundamental").exists(); + let mut flags = StructFlags::NO_FLAGS; + if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() { + flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL; + } + if attrs.by_key("fundamental").exists() { + flags |= StructFlags::IS_FUNDAMENTAL; + } let union = &item_tree[loc.id.value]; let (variant_data, diagnostics) = lower_fields( @@ -239,8 +277,7 @@ pub(crate) fn union_data_with_diagnostics_query( variant_data: Arc::new(variant_data), repr, visibility: item_tree[union.visibility].clone(), - rustc_has_incoherent_inherent_impls, - fundamental, + flags, }), diagnostics.into(), ) @@ -436,7 +473,7 @@ fn lower_struct( trace: &mut Trace>, ast: &InFile, ) -> StructKind { - let ctx = LowerCtx::new(db, ast.file_id); + let ctx = LowerCtx::new(db, &expander.hygiene(), ast.file_id); match &ast.value { ast::StructKind::Tuple(fl) => { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index 9371fc14dd8a..6d18e3f56cab 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -1,32 +1,32 @@ //! Defines database & queries for name resolution. -use std::sync::Arc; - use base_db::{salsa, CrateId, SourceDatabase, Upcast}; use either::Either; use hir_expand::{db::ExpandDatabase, HirFileId}; use intern::Interned; use la_arena::ArenaMap; use syntax::{ast, AstPtr}; +use triomphe::Arc; use crate::{ - adt::{EnumData, StructData}, attr::{Attrs, AttrsWithOwner}, body::{scope::ExprScopes, Body, BodySourceMap}, data::{ + adt::{EnumData, StructData}, ConstData, FunctionData, ImplData, Macro2Data, MacroRulesData, ProcMacroData, StaticData, TraitAliasData, TraitData, TypeAliasData, }, generics::GenericParams, + hir::ExprId, import_map::ImportMap, item_tree::{AttrOwner, ItemTree}, lang_item::{LangItem, LangItemTarget, LangItems}, nameres::{diagnostics::DefDiagnostic, DefMap}, visibility::{self, Visibility}, - AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, - ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalEnumVariantId, - LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, - StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, - TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, VariantId, + AnonymousConstId, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, + EnumLoc, ExternBlockId, ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, + LocalEnumVariantId, LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, + ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, + TraitAliasLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, VariantId, }; #[salsa::query_group(InternDatabaseStorage)] @@ -61,12 +61,14 @@ pub trait InternDatabase: SourceDatabase { fn intern_proc_macro(&self, loc: ProcMacroLoc) -> ProcMacroId; #[salsa::interned] fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId; + #[salsa::interned] + fn intern_anonymous_const(&self, id: (DefWithBodyId, ExprId)) -> AnonymousConstId; } #[salsa::query_group(DefDatabaseStorage)] pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast { #[salsa::input] - fn enable_proc_attr_macros(&self) -> bool; + fn expand_proc_attr_macros(&self) -> bool; #[salsa::invoke(ItemTree::file_item_tree_query)] fn file_item_tree(&self, file_id: HirFileId) -> Arc; @@ -94,7 +96,9 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast Option>; + fn block_def_map(&self, block: BlockId) -> Arc; + + // region:data #[salsa::invoke(StructData::struct_data_query)] fn struct_data(&self, id: StructId) -> Arc; @@ -151,6 +155,8 @@ fn struct_data_with_diagnostics(&self, id: StructId) #[salsa::invoke(ProcMacroData::proc_macro_data_query)] fn proc_macro_data(&self, makro: ProcMacroId) -> Arc; + // endregion:data + #[salsa::invoke(Body::body_with_source_map_query)] fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc, Arc); @@ -163,6 +169,8 @@ fn struct_data_with_diagnostics(&self, id: StructId) #[salsa::invoke(GenericParams::generic_params_query)] fn generic_params(&self, def: GenericDefId) -> Interned; + // region:attrs + #[salsa::invoke(Attrs::variants_attrs_query)] fn variants_attrs(&self, def: EnumId) -> Arc>; @@ -182,10 +190,13 @@ fn fields_attrs_source_map( ) -> Arc, AstPtr>>>; #[salsa::invoke(AttrsWithOwner::attrs_query)] - fn attrs(&self, def: AttrDefId) -> AttrsWithOwner; + fn attrs(&self, def: AttrDefId) -> Attrs; - #[salsa::invoke(LangItems::crate_lang_items_query)] - fn crate_lang_items(&self, krate: CrateId) -> Arc; + #[salsa::transparent] + #[salsa::invoke(AttrsWithOwner::attrs_with_owner)] + fn attrs_with_owner(&self, def: AttrDefId) -> AttrsWithOwner; + + // endregion:attrs #[salsa::invoke(LangItems::lang_item_query)] fn lang_item(&self, start_crate: CrateId, item: LangItem) -> Option; @@ -193,6 +204,8 @@ fn fields_attrs_source_map( #[salsa::invoke(ImportMap::import_map_query)] fn import_map(&self, krate: CrateId) -> Arc; + // region:visibilities + #[salsa::invoke(visibility::field_visibilities_query)] fn field_visibilities(&self, var: VariantId) -> Arc>; @@ -203,9 +216,17 @@ fn fields_attrs_source_map( #[salsa::invoke(visibility::const_visibility_query)] fn const_visibility(&self, def: ConstId) -> Visibility; + // endregion:visibilities + + #[salsa::invoke(LangItems::crate_lang_items_query)] + fn crate_lang_items(&self, krate: CrateId) -> Arc; + #[salsa::transparent] fn crate_limits(&self, crate_id: CrateId) -> CrateLimits; + #[salsa::transparent] + fn recursion_limit(&self, crate_id: CrateId) -> u32; + fn crate_supports_no_std(&self, crate_id: CrateId) -> bool; } @@ -228,6 +249,10 @@ fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits { } } +fn recursion_limit(db: &dyn DefDatabase, crate_id: CrateId) -> u32 { + db.crate_limits(crate_id).recursion_limit +} + fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool { let file = db.crate_graph()[crate_id].root_file_id; let item_tree = db.file_item_tree(file.into()); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs index 166aa04da044..63138aa6ad78 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs @@ -21,6 +21,8 @@ //! //! This is a work of fiction. Any similarities to Kotlin's `BindingContext` are //! a coincidence. +pub mod keys; + use std::{ hash::Hash, marker::PhantomData, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/keys.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs similarity index 100% rename from src/tools/rust-analyzer/crates/hir-def/src/keys.rs rename to src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expander.rs b/src/tools/rust-analyzer/crates/hir-def/src/expander.rs new file mode 100644 index 000000000000..34ed1e72f200 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/expander.rs @@ -0,0 +1,211 @@ +//! Macro expansion utilities. + +use base_db::CrateId; +use cfg::CfgOptions; +use drop_bomb::DropBomb; +use hir_expand::{ + attrs::RawAttrs, hygiene::Hygiene, mod_path::ModPath, ExpandError, ExpandResult, HirFileId, + InFile, MacroCallId, UnresolvedMacro, +}; +use limit::Limit; +use syntax::{ast, Parse, SyntaxNode}; + +use crate::{ + attr::Attrs, db::DefDatabase, lower::LowerCtx, macro_id_to_def_id, path::Path, AsMacroCall, + MacroId, ModuleId, +}; + +/// A subset of Expander that only deals with cfg attributes. We only need it to +/// avoid cyclic queries in crate def map during enum processing. +#[derive(Debug)] +pub(crate) struct CfgExpander { + cfg_options: CfgOptions, + hygiene: Hygiene, + krate: CrateId, +} + +#[derive(Debug)] +pub struct Expander { + cfg_expander: CfgExpander, + pub(crate) current_file_id: HirFileId, + pub(crate) module: ModuleId, + /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. + recursion_depth: u32, + recursion_limit: Limit, +} + +impl CfgExpander { + pub(crate) fn new( + db: &dyn DefDatabase, + current_file_id: HirFileId, + krate: CrateId, + ) -> CfgExpander { + let hygiene = Hygiene::new(db.upcast(), current_file_id); + let cfg_options = db.crate_graph()[krate].cfg_options.clone(); + CfgExpander { cfg_options, hygiene, krate } + } + + pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { + Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene)) + } + + pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool { + let attrs = self.parse_attrs(db, owner); + attrs.is_cfg_enabled(&self.cfg_options) + } + + pub(crate) fn hygiene(&self) -> &Hygiene { + &self.hygiene + } +} + +impl Expander { + pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander { + let cfg_expander = CfgExpander::new(db, current_file_id, module.krate); + let recursion_limit = db.recursion_limit(module.krate); + #[cfg(not(test))] + let recursion_limit = Limit::new(recursion_limit as usize); + // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug + #[cfg(test)] + let recursion_limit = Limit::new(std::cmp::min(32, recursion_limit as usize)); + Expander { cfg_expander, current_file_id, module, recursion_depth: 0, recursion_limit } + } + + pub fn enter_expand( + &mut self, + db: &dyn DefDatabase, + macro_call: ast::MacroCall, + resolver: impl Fn(ModPath) -> Option, + ) -> Result)>>, UnresolvedMacro> { + // FIXME: within_limit should support this, instead of us having to extract the error + let mut unresolved_macro_err = None; + + let result = self.within_limit(db, |this| { + let macro_call = InFile::new(this.current_file_id, ¯o_call); + match macro_call.as_call_id_with_errors(db.upcast(), this.module.krate(), |path| { + resolver(path).map(|it| macro_id_to_def_id(db, it)) + }) { + Ok(call_id) => call_id, + Err(resolve_err) => { + unresolved_macro_err = Some(resolve_err); + ExpandResult { value: None, err: None } + } + } + }); + + if let Some(err) = unresolved_macro_err { + Err(err) + } else { + Ok(result) + } + } + + pub fn enter_expand_id( + &mut self, + db: &dyn DefDatabase, + call_id: MacroCallId, + ) -> ExpandResult)>> { + self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) + } + + fn enter_expand_inner( + db: &dyn DefDatabase, + call_id: MacroCallId, + error: Option, + ) -> ExpandResult>>> { + let file_id = call_id.as_file(); + let ExpandResult { value, err } = db.parse_or_expand_with_err(file_id); + + ExpandResult { value: Some(InFile::new(file_id, value)), err: error.or(err) } + } + + pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { + self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); + self.current_file_id = mark.file_id; + if self.recursion_depth == u32::MAX { + // Recursion limit has been reached somewhere in the macro expansion tree. Reset the + // depth only when we get out of the tree. + if !self.current_file_id.is_macro() { + self.recursion_depth = 0; + } + } else { + self.recursion_depth -= 1; + } + mark.bomb.defuse(); + } + + pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> { + LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id) + } + + pub(crate) fn to_source(&self, value: T) -> InFile { + InFile { file_id: self.current_file_id, value } + } + + pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { + self.cfg_expander.parse_attrs(db, owner) + } + + pub(crate) fn cfg_options(&self) -> &CfgOptions { + &self.cfg_expander.cfg_options + } + + pub fn current_file_id(&self) -> HirFileId { + self.current_file_id + } + + pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option { + let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene); + Path::from_src(path, &ctx) + } + + fn within_limit( + &mut self, + db: &dyn DefDatabase, + op: F, + ) -> ExpandResult)>> + where + F: FnOnce(&mut Self) -> ExpandResult>, + { + if self.recursion_depth == u32::MAX { + // Recursion limit has been reached somewhere in the macro expansion tree. We should + // stop expanding other macro calls in this tree, or else this may result in + // exponential number of macro expansions, leading to a hang. + // + // The overflow error should have been reported when it occurred (see the next branch), + // so don't return overflow error here to avoid diagnostics duplication. + cov_mark::hit!(overflow_but_not_me); + return ExpandResult::only_err(ExpandError::RecursionOverflowPoisoned); + } else if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() { + self.recursion_depth = u32::MAX; + cov_mark::hit!(your_stack_belongs_to_me); + return ExpandResult::only_err(ExpandError::Other( + "reached recursion limit during macro expansion".into(), + )); + } + + let ExpandResult { value, err } = op(self); + let Some(call_id) = value else { + return ExpandResult { value: None, err }; + }; + + Self::enter_expand_inner(db, call_id, err).map(|value| { + value.and_then(|InFile { file_id, value }| { + let parse = value.cast::()?; + + self.recursion_depth += 1; + self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id); + let old_file_id = std::mem::replace(&mut self.current_file_id, file_id); + let mark = + Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") }; + Some((mark, parse)) + }) + }) + } +} + +#[derive(Debug)] +pub struct Mark { + file_id: HirFileId, + bomb: DropBomb, +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs index 3f4392320837..e8cc2eab4617 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs @@ -42,7 +42,7 @@ pub fn find_path_prefixed( #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PrefixKind { /// Causes paths to always start with either `self`, `super`, `crate` or a crate-name. - /// This is the same as plain, just that paths will start with `self` iprepended f the path + /// This is the same as plain, just that paths will start with `self` prepended if the path /// starts with an identifier that is not a crate. BySelf, /// Causes paths to ignore imports in the local module. @@ -81,7 +81,7 @@ fn find_path_inner( } let def_map = from.def_map(db); - let crate_root = def_map.crate_root(db); + let crate_root = def_map.crate_root(); // - if the item is a module, jump straight to module search if let ItemInNs::Types(ModuleDefId::ModuleId(module_id)) = item { let mut visited_modules = FxHashSet::default(); @@ -183,7 +183,7 @@ fn find_path_for_module( // - if the item is the crate root of a dependency crate, return the name from the extern prelude let root_def_map = crate_root.def_map(db); - for (name, &def_id) in root_def_map.extern_prelude() { + for (name, def_id) in root_def_map.extern_prelude() { if module_id == def_id { let name = scope_name.unwrap_or_else(|| name.clone()); @@ -454,7 +454,7 @@ fn find_local_import_locations( worklist.push(ancestor); } - let def_map = def_map.crate_root(db).def_map(db); + let def_map = def_map.crate_root().def_map(db); let mut seen: FxHashSet<_> = FxHashSet::default(); @@ -543,6 +543,7 @@ fn check_found_path_(ra_fixture: &str, path: &str, prefix_kind: Option Interned { let _p = profile::span("generic_params_query"); - macro_rules! id_to_generics { ($id:ident) => {{ let id = $id.lookup(db).id; @@ -176,8 +176,10 @@ macro_rules! id_to_generics { // Don't create an `Expander` nor call `loc.source(db)` if not needed since this // causes a reparse after the `ItemTree` has been created. - let mut expander = Lazy::new(|| Expander::new(db, loc.source(db).file_id, module)); - for (_, param) in &func_data.params { + let mut expander = Lazy::new(|| { + (module.def_map(db), Expander::new(db, loc.source(db).file_id, module)) + }); + for param in &func_data.params { generic_params.fill_implicit_impl_trait_args(db, &mut expander, param); } @@ -329,7 +331,7 @@ fn add_where_predicate_from_bound( pub(crate) fn fill_implicit_impl_trait_args( &mut self, db: &dyn DefDatabase, - expander: &mut impl DerefMut, + exp: &mut Lazy<(Arc, Expander), impl FnOnce() -> (Arc, Expander)>, type_ref: &TypeRef, ) { type_ref.walk(&mut |type_ref| { @@ -349,14 +351,28 @@ pub(crate) fn fill_implicit_impl_trait_args( } if let TypeRef::Macro(mc) = type_ref { let macro_call = mc.to_node(db.upcast()); - match expander.enter_expand::(db, macro_call) { - Ok(ExpandResult { value: Some((mark, expanded)), .. }) => { - let ctx = LowerCtx::new(db, expander.current_file_id()); - let type_ref = TypeRef::from_ast(&ctx, expanded); - self.fill_implicit_impl_trait_args(db, expander, &type_ref); - expander.exit(db, mark); - } - _ => {} + let (def_map, expander) = &mut **exp; + + let module = expander.module.local_id; + let resolver = |path| { + def_map + .resolve_path( + db, + module, + &path, + crate::item_scope::BuiltinShadowMode::Other, + Some(MacroSubNs::Bang), + ) + .0 + .take_macros() + }; + if let Ok(ExpandResult { value: Some((mark, expanded)), .. }) = + expander.enter_expand(db, macro_call, resolver) + { + let ctx = expander.ctx(db); + let type_ref = TypeRef::from_ast(&ctx, expanded.tree()); + self.fill_implicit_impl_trait_args(db, &mut *exp, &type_ref); + exp.1.exit(db, mark); } } }); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs similarity index 76% rename from src/tools/rust-analyzer/crates/hir-def/src/expr.rs rename to src/tools/rust-analyzer/crates/hir-def/src/hir.rs index 19fa6b25419e..4ad8a7aa8eb1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs @@ -12,26 +12,29 @@ //! //! See also a neighboring `body` module. +pub mod type_ref; + use std::fmt; use hir_expand::name::Name; use intern::Interned; use la_arena::{Idx, RawIdx}; use smallvec::SmallVec; +use syntax::ast; use crate::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, - BlockId, + AnonymousConstId, BlockId, }; pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}; -pub type ExprId = Idx; - pub type BindingId = Idx; +pub type ExprId = Idx; + /// FIXME: this is a hacky function which should be removed pub(crate) fn dummy_expr_id() -> ExprId { ExprId::from_raw(RawIdx::from(u32::MAX)) @@ -82,6 +85,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub enum Literal { String(Box), ByteString(Box<[u8]>), + CString(Box), Char(char), Bool(bool), Int(i128, Option), @@ -92,6 +96,66 @@ pub enum Literal { Float(FloatTypeWrapper, Option), } +#[derive(Debug, Clone, Eq, PartialEq)] +/// Used in range patterns. +pub enum LiteralOrConst { + Literal(Literal), + Const(Path), +} + +impl Literal { + pub fn negate(self) -> Option { + if let Literal::Int(i, k) = self { + Some(Literal::Int(-i, k)) + } else { + None + } + } +} + +impl From for Literal { + fn from(ast_lit_kind: ast::LiteralKind) -> Self { + use ast::LiteralKind; + match ast_lit_kind { + // FIXME: these should have actual values filled in, but unsure on perf impact + LiteralKind::IntNumber(lit) => { + if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { + Literal::Float( + FloatTypeWrapper::new(lit.float_value().unwrap_or(Default::default())), + builtin, + ) + } else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinUint::from_suffix) { + Literal::Uint(lit.value().unwrap_or(0), builtin) + } else { + let builtin = lit.suffix().and_then(BuiltinInt::from_suffix); + Literal::Int(lit.value().unwrap_or(0) as i128, builtin) + } + } + LiteralKind::FloatNumber(lit) => { + let ty = lit.suffix().and_then(BuiltinFloat::from_suffix); + Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty) + } + LiteralKind::ByteString(bs) => { + let text = bs.value().map(Box::from).unwrap_or_else(Default::default); + Literal::ByteString(text) + } + LiteralKind::String(s) => { + let text = s.value().map(Box::from).unwrap_or_else(Default::default); + Literal::String(text) + } + LiteralKind::CString(s) => { + let text = s.value().map(Box::from).unwrap_or_else(Default::default); + Literal::CString(text) + } + LiteralKind::Byte(b) => { + Literal::Uint(b.value().unwrap_or_default() as u128, Some(BuiltinUint::U8)) + } + LiteralKind::Char(c) => Literal::Char(c.value().unwrap_or_default()), + LiteralKind::Bool(val) => Literal::Bool(val), + } + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Expr { /// This is produced if the syntax tree does not have a required expression piece. @@ -107,28 +171,19 @@ pub enum Expr { expr: ExprId, }, Block { - id: BlockId, + id: Option, statements: Box<[Statement]>, tail: Option, label: Option, }, - TryBlock { - id: BlockId, - statements: Box<[Statement]>, - tail: Option, - }, Async { - id: BlockId, - statements: Box<[Statement]>, - tail: Option, - }, - Const { - id: BlockId, + id: Option, statements: Box<[Statement]>, tail: Option, }, + Const(AnonymousConstId), Unsafe { - id: BlockId, + id: Option, statements: Box<[Statement]>, tail: Option, }, @@ -141,12 +196,6 @@ pub enum Expr { body: ExprId, label: Option, }, - For { - iterable: ExprId, - pat: PatId, - body: ExprId, - label: Option, - }, Call { callee: ExprId, args: Box<[ExprId]>, @@ -163,11 +212,11 @@ pub enum Expr { arms: Box<[MatchArm]>, }, Continue { - label: Option, + label: Option, }, Break { expr: Option, - label: Option, + label: Option, }, Return { expr: Option, @@ -192,9 +241,6 @@ pub enum Expr { Await { expr: ExprId, }, - Try { - expr: ExprId, - }, Cast { expr: ExprId, type_ref: Interned, @@ -231,6 +277,7 @@ pub enum Expr { ret_type: Option>, body: ExprId, closure_kind: ClosureKind, + capture_by: CaptureBy, }, Tuple { exprs: Box<[ExprId]>, @@ -248,6 +295,14 @@ pub enum ClosureKind { Async, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CaptureBy { + /// `move |x| y + x`. + Value, + /// `move` keyword was not specified. + Ref, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Movability { Static, @@ -302,11 +357,10 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { Expr::Let { expr, .. } => { f(*expr); } + Expr::Const(_) => (), Expr::Block { statements, tail, .. } - | Expr::TryBlock { statements, tail, .. } | Expr::Unsafe { statements, tail, .. } - | Expr::Async { statements, tail, .. } - | Expr::Const { statements, tail, .. } => { + | Expr::Async { statements, tail, .. } => { for stmt in statements.iter() { match stmt { Statement::Let { initializer, else_branch, .. } => { @@ -329,10 +383,6 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { f(*condition); f(*body); } - Expr::For { iterable, body, .. } => { - f(*iterable); - f(*body); - } Expr::Call { callee, args, .. } => { f(*callee); args.iter().copied().for_each(f); @@ -383,7 +433,6 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { } Expr::Field { expr, .. } | Expr::Await { expr } - | Expr::Try { expr } | Expr::Cast { expr, .. } | Expr::Ref { expr, .. } | Expr::UnaryOp { expr, .. } @@ -437,11 +486,38 @@ pub fn new(is_mutable: bool, is_ref: bool) -> Self { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum BindingProblems { + /// https://doc.rust-lang.org/stable/error_codes/E0416.html + BoundMoreThanOnce, + /// https://doc.rust-lang.org/stable/error_codes/E0409.html + BoundInconsistently, + /// https://doc.rust-lang.org/stable/error_codes/E0408.html + NotBoundAcrossAll, +} + #[derive(Debug, Clone, Eq, PartialEq)] pub struct Binding { pub name: Name, pub mode: BindingAnnotation, pub definitions: SmallVec<[PatId; 1]>, + /// Id of the closure/generator that owns this binding. If it is owned by the + /// top level expression, this field would be `None`. + pub owner: Option, + pub problems: Option, +} + +impl Binding { + pub fn is_upvar(&self, relative_to: ExprId) -> bool { + match self.owner { + Some(x) => { + // We assign expression ids in a way that outer closures will receive + // a lower id + x.into_raw() < relative_to.into_raw() + } + None => true, + } + } } #[derive(Debug, Clone, Eq, PartialEq)] @@ -458,7 +534,7 @@ pub enum Pat { Tuple { args: Box<[PatId]>, ellipsis: Option }, Or(Box<[PatId]>), Record { path: Option>, args: Box<[RecordFieldPat]>, ellipsis: bool }, - Range { start: ExprId, end: ExprId }, + Range { start: Option>, end: Option> }, Slice { prefix: Box<[PatId]>, slice: Option, suffix: Box<[PatId]> }, Path(Box), Lit(ExprId), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs similarity index 96% rename from src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs rename to src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs index 8e30f429a9f8..0573c9a6f8af 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs @@ -1,9 +1,11 @@ //! HIR for references to types. Paths in these are not yet resolved. They can //! be directly created from an ast::TypeRef, without further queries. +use core::fmt; use std::fmt::Write; use hir_expand::{ + db::ExpandDatabase, name::{AsName, Name}, AstId, }; @@ -11,9 +13,9 @@ use syntax::ast::{self, HasName}; use crate::{ - body::LowerCtx, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, - expr::Literal, + hir::Literal, + lower::LowerCtx, path::Path, }; @@ -383,15 +385,6 @@ pub enum ConstRefOrPath { Path(Name), } -impl std::fmt::Display for ConstRefOrPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ConstRefOrPath::Scalar(s) => s.fmt(f), - ConstRefOrPath::Path(n) => n.fmt(f), - } - } -} - impl ConstRefOrPath { pub(crate) fn from_expr_opt(expr: Option) -> Self { match expr { @@ -400,6 +393,19 @@ pub(crate) fn from_expr_opt(expr: Option) -> Self { } } + pub fn display<'a>(&'a self, db: &'a dyn ExpandDatabase) -> impl fmt::Display + 'a { + struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRefOrPath); + impl fmt::Display for Display<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.1 { + ConstRefOrPath::Scalar(s) => s.fmt(f), + ConstRefOrPath::Path(n) => n.display(self.0).fmt(f), + } + } + } + Display(db, self) + } + // FIXME: as per the comments on `TypeRef::Array`, this evaluation should not happen at this // parse stage. fn from_expr(expr: ast::Expr) -> Self { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs index 4f1f6000db0e..48532655e04b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs @@ -1,6 +1,6 @@ //! A map of all publicly exported items in a crate. -use std::{fmt, hash::BuildHasherDefault, sync::Arc}; +use std::{fmt, hash::BuildHasherDefault}; use base_db::CrateId; use fst::{self, Streamer}; @@ -8,10 +8,11 @@ use indexmap::{map::Entry, IndexMap}; use itertools::Itertools; use rustc_hash::{FxHashSet, FxHasher}; +use triomphe::Arc; use crate::{ - db::DefDatabase, item_scope::ItemInNs, visibility::Visibility, AssocItemId, ModuleDefId, - ModuleId, TraitId, + db::DefDatabase, item_scope::ItemInNs, nameres::DefMap, visibility::Visibility, AssocItemId, + ModuleDefId, ModuleId, TraitId, }; type FxIndexMap = IndexMap>; @@ -32,13 +33,23 @@ pub struct ImportPath { pub segments: Vec, } -impl fmt::Display for ImportPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.segments.iter().format("::"), f) - } -} - impl ImportPath { + pub fn display<'a>(&'a self, db: &'a dyn DefDatabase) -> impl fmt::Display + 'a { + struct Display<'a> { + db: &'a dyn DefDatabase, + path: &'a ImportPath, + } + impl fmt::Display for Display<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt( + &self.path.segments.iter().map(|it| it.display(self.db.upcast())).format("::"), + f, + ) + } + } + Display { db, path: self } + } + fn len(&self) -> usize { self.segments.len() } @@ -75,7 +86,7 @@ pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc { let mut importables = import_map .map .iter() - .map(|(item, info)| (item, fst_path(&info.path))) + .map(|(item, info)| (item, fst_path(db, &info.path))) .collect::>(); importables.sort_by(|(_, fst_path), (_, fst_path2)| fst_path.cmp(fst_path2)); @@ -112,6 +123,25 @@ pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { self.map.get(&item) } + #[cfg(test)] + fn fmt_for_test(&self, db: &dyn DefDatabase) -> String { + let mut importable_paths: Vec<_> = self + .map + .iter() + .map(|(item, info)| { + let ns = match item { + ItemInNs::Types(_) => "t", + ItemInNs::Values(_) => "v", + ItemInNs::Macros(_) => "m", + }; + format!("- {} ({ns})", info.path.display(db)) + }) + .collect(); + + importable_paths.sort(); + importable_paths.join("\n") + } + fn collect_trait_assoc_items( &mut self, db: &dyn DefDatabase, @@ -153,7 +183,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap { // We look only into modules that are public(ly reexported), starting with the crate root. let empty = ImportPath { segments: vec![] }; - let root = def_map.module_id(def_map.root()); + let root = def_map.module_id(DefMap::ROOT); let mut worklist = vec![(root, empty)]; while let Some((module, mod_path)) = worklist.pop() { let ext_def_map; @@ -233,13 +263,10 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut importable_paths: Vec<_> = self .map .iter() - .map(|(item, info)| { - let ns = match item { - ItemInNs::Types(_) => "t", - ItemInNs::Values(_) => "v", - ItemInNs::Macros(_) => "m", - }; - format!("- {} ({ns})", info.path) + .map(|(item, _)| match item { + ItemInNs::Types(it) => format!("- {it:?} (t)",), + ItemInNs::Values(it) => format!("- {it:?} (v)",), + ItemInNs::Macros(it) => format!("- {it:?} (m)",), }) .collect(); @@ -248,9 +275,9 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } -fn fst_path(path: &ImportPath) -> String { +fn fst_path(db: &dyn DefDatabase, path: &ImportPath) -> String { let _p = profile::span("fst_path"); - let mut s = path.to_string(); + let mut s = path.display(db).to_string(); s.make_ascii_lowercase(); s } @@ -343,7 +370,12 @@ pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self { self } - fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool { + fn import_matches( + &self, + db: &dyn DefDatabase, + import: &ImportInfo, + enforce_lowercase: bool, + ) -> bool { let _p = profile::span("import_map::Query::import_matches"); if import.is_trait_assoc_item { if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) { @@ -354,9 +386,9 @@ fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool { } let mut input = if import.is_trait_assoc_item || self.name_only { - import.path.segments.last().unwrap().to_string() + import.path.segments.last().unwrap().display(db.upcast()).to_string() } else { - import.path.to_string() + import.path.display(db).to_string() }; if enforce_lowercase || !self.case_sensitive { input.make_ascii_lowercase(); @@ -421,25 +453,27 @@ pub fn search_dependencies( let importables = &import_map.importables[indexed_value.value as usize..]; let common_importable_data = &import_map.map[&importables[0]]; - if !query.import_matches(common_importable_data, true) { + if !query.import_matches(db, common_importable_data, true) { continue; } // Path shared by the importable items in this group. - let common_importables_path_fst = fst_path(&common_importable_data.path); + let common_importables_path_fst = fst_path(db, &common_importable_data.path); // Add the items from this `ModPath` group. Those are all subsequent items in // `importables` whose paths match `path`. let iter = importables .iter() .copied() - .take_while(|item| common_importables_path_fst == fst_path(&import_map.map[item].path)) + .take_while(|item| { + common_importables_path_fst == fst_path(db, &import_map.map[item].path) + }) .filter(|&item| match item_import_kind(item) { Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind), None => true, }) .filter(|item| { !query.case_sensitive // we've already checked the common importables path case-insensitively - || query.import_matches(&import_map.map[item], false) + || query.import_matches(db, &import_map.map[item], false) }); res.extend(iter); @@ -472,7 +506,7 @@ mod tests { use base_db::{fixture::WithFixture, SourceDatabase, Upcast}; use expect_test::{expect, Expect}; - use crate::{test_db::TestDB, ItemContainerId, Lookup}; + use crate::{db::DefDatabase, test_db::TestDB, ItemContainerId, Lookup}; use super::*; @@ -496,7 +530,7 @@ fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) { Some(assoc_item_path) => (assoc_item_path, "a"), None => ( - dependency_imports.path_of(dependency)?.to_string(), + dependency_imports.path_of(dependency)?.display(&db).to_string(), match dependency { ItemInNs::Types(ModuleDefId::FunctionId(_)) | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f", @@ -547,7 +581,11 @@ fn assoc_item_path( None } })?; - return Some(format!("{}::{assoc_item_name}", dependency_imports.path_of(trait_)?)); + return Some(format!( + "{}::{}", + dependency_imports.path_of(trait_)?.display(db), + assoc_item_name.display(db.upcast()) + )); } None } @@ -587,7 +625,7 @@ fn check(ra_fixture: &str, expect: Expect) { let map = db.import_map(krate); - Some(format!("{name}:\n{map:?}\n")) + Some(format!("{name}:\n{}\n", map.fmt_for_test(db.upcast()))) }) .sorted() .collect::(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index 991e447033fe..3ed321d189d3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -4,7 +4,7 @@ use std::collections::hash_map::Entry; use base_db::CrateId; -use hir_expand::{attrs::AttrId, name::Name, AstId, MacroCallId}; +use hir_expand::{attrs::AttrId, db::ExpandDatabase, name::Name, AstId, MacroCallId}; use itertools::Itertools; use once_cell::sync::Lazy; use profile::Count; @@ -358,12 +358,16 @@ pub(crate) fn censor_non_proc_macros(&mut self, this_module: ModuleId) { } } - pub(crate) fn dump(&self, buf: &mut String) { + pub(crate) fn dump(&self, db: &dyn ExpandDatabase, buf: &mut String) { let mut entries: Vec<_> = self.resolutions().collect(); entries.sort_by_key(|(name, _)| name.clone()); for (name, def) in entries { - format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string())); + format_to!( + buf, + "{}:", + name.map_or("_".to_string(), |name| name.display(db).to_string()) + ); if def.types.is_some() { buf.push_str(" t"); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index 9da5b2d47c87..590ed64af5d8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -40,7 +40,6 @@ hash::{Hash, Hasher}, marker::PhantomData, ops::Index, - sync::Arc, }; use ast::{AstNode, HasName, StructKind}; @@ -60,6 +59,7 @@ use smallvec::SmallVec; use stdx::never; use syntax::{ast, match_ast, SyntaxKind}; +use triomphe::Arc; use crate::{ attr::Attrs, @@ -101,16 +101,14 @@ pub struct ItemTree { top_level: SmallVec<[ModItem; 1]>, attrs: FxHashMap, + // FIXME: Remove this indirection, an item tree is almost always non-empty? data: Option>, } impl ItemTree { pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc { let _p = profile::span("file_item_tree_query").detail(|| format!("{file_id:?}")); - let syntax = match db.parse_or_expand(file_id) { - Some(node) => node, - None => return Default::default(), - }; + let syntax = db.parse_or_expand(file_id); if never!(syntax.kind() == SyntaxKind::ERROR, "{:?} from {:?} {}", file_id, syntax, syntax) { // FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic @@ -169,8 +167,8 @@ pub(crate) fn attrs(&self, db: &dyn DefDatabase, krate: CrateId, of: AttrOwner) Attrs::filter(db, krate, self.raw_attrs(of).clone()) } - pub fn pretty_print(&self) -> String { - pretty::print_item_tree(self) + pub fn pretty_print(&self, db: &dyn DefDatabase) -> String { + pretty::print_item_tree(db.upcast(), self) } fn data(&self) -> &ItemTreeData { @@ -600,19 +598,18 @@ pub struct Function { pub abi: Option>, pub params: IdxRange, pub ret_type: Interned, - pub async_ret_type: Option>, pub ast_id: FileAstId, pub(crate) flags: FnFlags, } #[derive(Debug, Clone, Eq, PartialEq)] pub enum Param { - Normal(Option, Interned), + Normal(Interned), Varargs, } bitflags::bitflags! { - #[derive(Default)] + #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub(crate) struct FnFlags: u8 { const HAS_SELF_PARAM = 1 << 0; const HAS_BODY = 1 << 1; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs index 77b186f8e3fe..46633667ed3e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -1,6 +1,6 @@ //! AST -> `ItemTree` lowering code. -use std::{collections::hash_map::Entry, sync::Arc}; +use std::collections::hash_map::Entry; use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; use syntax::ast::{self, HasModuleItem, HasTypeBounds}; @@ -20,7 +20,7 @@ pub(super) struct Ctx<'a> { db: &'a dyn DefDatabase, tree: ItemTree, source_ast_id_map: Arc, - body_ctx: crate::body::LowerCtx<'a>, + body_ctx: crate::lower::LowerCtx<'a>, } impl<'a> Ctx<'a> { @@ -29,7 +29,7 @@ pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId) -> Self { db, tree: ItemTree::default(), source_ast_id_map: db.ast_id_map(file), - body_ctx: crate::body::LowerCtx::new(db, file), + body_ctx: crate::lower::LowerCtx::with_file_id(db, file), } } @@ -293,7 +293,7 @@ fn lower_function(&mut self, func: &ast::Fn) -> Option> } }; let ty = Interned::new(self_type); - let idx = self.data().params.alloc(Param::Normal(None, ty)); + let idx = self.data().params.alloc(Param::Normal(ty)); self.add_attrs( idx.into(), RawAttrs::new(self.db.upcast(), &self_param, self.hygiene()), @@ -306,19 +306,7 @@ fn lower_function(&mut self, func: &ast::Fn) -> Option> None => { let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty()); let ty = Interned::new(type_ref); - let mut pat = param.pat(); - // FIXME: This really shouldn't be here, in fact FunctionData/ItemTree's function shouldn't know about - // pattern names at all - let name = 'name: loop { - match pat { - Some(ast::Pat::RefPat(ref_pat)) => pat = ref_pat.pat(), - Some(ast::Pat::IdentPat(ident)) => { - break 'name ident.name().map(|it| it.as_name()) - } - _ => break 'name None, - } - }; - self.data().params.alloc(Param::Normal(name, ty)) + self.data().params.alloc(Param::Normal(ty)) } }; self.add_attrs(idx.into(), RawAttrs::new(self.db.upcast(), ¶m, self.hygiene())); @@ -336,13 +324,12 @@ fn lower_function(&mut self, func: &ast::Fn) -> Option> None => TypeRef::unit(), }; - let (ret_type, async_ret_type) = if func.async_token().is_some() { - let async_ret_type = ret_type.clone(); + let ret_type = if func.async_token().is_some() { let future_impl = desugar_future_path(ret_type); let ty_bound = Interned::new(TypeBound::Path(future_impl, TraitBoundModifier::None)); - (TypeRef::ImplTrait(vec![ty_bound]), Some(async_ret_type)) + TypeRef::ImplTrait(vec![ty_bound]) } else { - (ret_type, None) + ret_type }; let abi = func.abi().map(lower_abi); @@ -376,7 +363,6 @@ fn lower_function(&mut self, func: &ast::Fn) -> Option> abi, params, ret_type: Interned::new(ret_type), - async_ret_type: async_ret_type.map(Interned::new), ast_id, flags, }; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs index 5f29997964b9..e873316a578c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs @@ -2,6 +2,8 @@ use std::fmt::{self, Write}; +use hir_expand::db::ExpandDatabase; + use crate::{ generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, pretty::{print_path, print_type_bounds, print_type_ref}, @@ -10,8 +12,8 @@ use super::*; -pub(super) fn print_item_tree(tree: &ItemTree) -> String { - let mut p = Printer { tree, buf: String::new(), indent_level: 0, needs_indent: true }; +pub(super) fn print_item_tree(db: &dyn ExpandDatabase, tree: &ItemTree) -> String { + let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true }; if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { p.print_attrs(attrs, true); @@ -43,6 +45,7 @@ macro_rules! wln { } struct Printer<'a> { + db: &'a dyn ExpandDatabase, tree: &'a ItemTree, buf: String, indent_level: usize, @@ -88,7 +91,7 @@ fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool) { self, "#{}[{}{}]", inner, - attr.path, + attr.path.display(self.db), attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), ); } @@ -102,7 +105,7 @@ fn print_attrs_of(&mut self, of: impl Into) { fn print_visibility(&mut self, vis: RawVisibilityId) { match &self.tree[vis] { - RawVisibility::Module(path) => w!(self, "pub({}) ", path), + RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db)), RawVisibility::Public => w!(self, "pub "), }; } @@ -117,7 +120,7 @@ fn print_fields(&mut self, fields: &Fields) { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field); this.print_visibility(*visibility); - w!(this, "{}: ", name); + w!(this, "{}: ", name.display(self.db)); this.print_type_ref(type_ref); wln!(this, ","); } @@ -131,7 +134,7 @@ fn print_fields(&mut self, fields: &Fields) { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field); this.print_visibility(*visibility); - w!(this, "{}: ", name); + w!(this, "{}: ", name.display(self.db)); this.print_type_ref(type_ref); wln!(this, ","); } @@ -164,20 +167,20 @@ fn print_fields_and_where_clause(&mut self, fields: &Fields, params: &GenericPar fn print_use_tree(&mut self, use_tree: &UseTree) { match &use_tree.kind { UseTreeKind::Single { path, alias } => { - w!(self, "{}", path); + w!(self, "{}", path.display(self.db)); if let Some(alias) = alias { w!(self, " as {}", alias); } } UseTreeKind::Glob { path } => { if let Some(path) = path { - w!(self, "{}::", path); + w!(self, "{}::", path.display(self.db)); } w!(self, "*"); } UseTreeKind::Prefixed { prefix, list } => { if let Some(prefix) = prefix { - w!(self, "{}::", prefix); + w!(self, "{}::", prefix.display(self.db)); } w!(self, "{{"); for (i, tree) in list.iter().enumerate() { @@ -205,7 +208,7 @@ fn print_mod_item(&mut self, item: ModItem) { ModItem::ExternCrate(it) => { let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "extern crate {}", name); + w!(self, "extern crate {}", name.display(self.db)); if let Some(alias) = alias { w!(self, " as {}", alias); } @@ -233,7 +236,6 @@ fn print_mod_item(&mut self, item: ModItem) { abi, params, ret_type, - async_ret_type: _, ast_id: _, flags, } = &self.tree[it]; @@ -253,26 +255,20 @@ fn print_mod_item(&mut self, item: ModItem) { if let Some(abi) = abi { w!(self, "extern \"{}\" ", abi); } - w!(self, "fn {}", name); + w!(self, "fn {}", name.display(self.db)); self.print_generic_params(explicit_generic_params); w!(self, "("); if !params.is_empty() { self.indented(|this| { - for (i, param) in params.clone().enumerate() { + for param in params.clone() { this.print_attrs_of(param); match &this.tree[param] { - Param::Normal(name, ty) => { - match name { - Some(name) => w!(this, "{}: ", name), - None => w!(this, "_: "), + Param::Normal(ty) => { + if flags.contains(FnFlags::HAS_SELF_PARAM) { + w!(this, "self: "); } this.print_type_ref(ty); - w!(this, ","); - if flags.contains(FnFlags::HAS_SELF_PARAM) && i == 0 { - wln!(this, " // self"); - } else { - wln!(this); - } + wln!(this, ","); } Param::Varargs => { wln!(this, "..."); @@ -293,7 +289,7 @@ fn print_mod_item(&mut self, item: ModItem) { ModItem::Struct(it) => { let Struct { visibility, name, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "struct {}", name); + w!(self, "struct {}", name.display(self.db)); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -305,7 +301,7 @@ fn print_mod_item(&mut self, item: ModItem) { ModItem::Union(it) => { let Union { name, visibility, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "union {}", name); + w!(self, "union {}", name.display(self.db)); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -317,14 +313,14 @@ fn print_mod_item(&mut self, item: ModItem) { ModItem::Enum(it) => { let Enum { name, visibility, variants, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "enum {}", name); + w!(self, "enum {}", name.display(self.db)); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { for variant in variants.clone() { let Variant { name, fields, ast_id: _ } = &this.tree[variant]; this.print_attrs_of(variant); - w!(this, "{}", name); + w!(this, "{}", name.display(self.db)); this.print_fields(fields); wln!(this, ","); } @@ -336,7 +332,7 @@ fn print_mod_item(&mut self, item: ModItem) { self.print_visibility(*visibility); w!(self, "const "); match name { - Some(name) => w!(self, "{}", name), + Some(name) => w!(self, "{}", name.display(self.db)), None => w!(self, "_"), } w!(self, ": "); @@ -350,7 +346,7 @@ fn print_mod_item(&mut self, item: ModItem) { if *mutable { w!(self, "mut "); } - w!(self, "{}: ", name); + w!(self, "{}: ", name.display(self.db)); self.print_type_ref(type_ref); w!(self, " = _;"); wln!(self); @@ -372,7 +368,7 @@ fn print_mod_item(&mut self, item: ModItem) { if *is_auto { w!(self, "auto "); } - w!(self, "trait {}", name); + w!(self, "trait {}", name.display(self.db)); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { @@ -385,7 +381,7 @@ fn print_mod_item(&mut self, item: ModItem) { ModItem::TraitAlias(it) => { let TraitAlias { name, visibility, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "trait {}", name); + w!(self, "trait {}", name.display(self.db)); self.print_generic_params(generic_params); w!(self, " = "); self.print_where_clause(generic_params); @@ -418,7 +414,7 @@ fn print_mod_item(&mut self, item: ModItem) { let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "type {}", name); + w!(self, "type {}", name.display(self.db)); self.print_generic_params(generic_params); if !bounds.is_empty() { w!(self, ": "); @@ -435,7 +431,7 @@ fn print_mod_item(&mut self, item: ModItem) { ModItem::Mod(it) => { let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "mod {}", name); + w!(self, "mod {}", name.display(self.db)); match kind { ModKind::Inline { items } => { w!(self, " {{"); @@ -453,16 +449,16 @@ fn print_mod_item(&mut self, item: ModItem) { } ModItem::MacroCall(it) => { let MacroCall { path, ast_id: _, expand_to: _ } = &self.tree[it]; - wln!(self, "{}!(...);", path); + wln!(self, "{}!(...);", path.display(self.db)); } ModItem::MacroRules(it) => { let MacroRules { name, ast_id: _ } = &self.tree[it]; - wln!(self, "macro_rules! {} {{ ... }}", name); + wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db)); } ModItem::MacroDef(it) => { let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - wln!(self, "macro {} {{ ... }}", name); + wln!(self, "macro {} {{ ... }}", name.display(self.db)); } } @@ -470,15 +466,15 @@ fn print_mod_item(&mut self, item: ModItem) { } fn print_type_ref(&mut self, type_ref: &TypeRef) { - print_type_ref(type_ref, self).unwrap(); + print_type_ref(self.db, type_ref, self).unwrap(); } fn print_type_bounds(&mut self, bounds: &[Interned]) { - print_type_bounds(bounds, self).unwrap(); + print_type_bounds(self.db, bounds, self).unwrap(); } fn print_path(&mut self, path: &Path) { - print_path(path, self).unwrap(); + print_path(self.db, path, self).unwrap(); } fn print_generic_params(&mut self, params: &GenericParams) { @@ -493,7 +489,7 @@ fn print_generic_params(&mut self, params: &GenericParams) { w!(self, ", "); } first = false; - w!(self, "{}", lt.name); + w!(self, "{}", lt.name.display(self.db)); } for (idx, x) in params.type_or_consts.iter() { if !first { @@ -502,11 +498,11 @@ fn print_generic_params(&mut self, params: &GenericParams) { first = false; match x { TypeOrConstParamData::TypeParamData(ty) => match &ty.name { - Some(name) => w!(self, "{}", name), + Some(name) => w!(self, "{}", name.display(self.db)), None => w!(self, "_anon_{}", idx.into_raw()), }, TypeOrConstParamData::ConstParamData(konst) => { - w!(self, "const {}: ", konst.name); + w!(self, "const {}: ", konst.name.display(self.db)); self.print_type_ref(&konst.ty); } } @@ -538,7 +534,12 @@ fn print_where_clause(&mut self, params: &GenericParams) -> bool { let (target, bound) = match pred { WherePredicate::TypeBound { target, bound } => (target, bound), WherePredicate::Lifetime { target, bound } => { - wln!(this, "{}: {},", target.name, bound.name); + wln!( + this, + "{}: {},", + target.name.display(self.db), + bound.name.display(self.db) + ); continue; } WherePredicate::ForLifetime { lifetimes, target, bound } => { @@ -547,7 +548,7 @@ fn print_where_clause(&mut self, params: &GenericParams) -> bool { if i != 0 { w!(this, ", "); } - w!(this, "{}", lt); + w!(this, "{}", lt.display(self.db)); } w!(this, "> "); (target, bound) @@ -558,7 +559,7 @@ fn print_where_clause(&mut self, params: &GenericParams) -> bool { WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty), WherePredicateTypeTarget::TypeOrConstParam(id) => { match ¶ms.type_or_consts[*id].name() { - Some(name) => w!(this, "{}", name), + Some(name) => w!(this, "{}", name.display(self.db)), None => w!(this, "_anon_{}", id.into_raw()), } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs index e30d9652bb5d..5ded4b6b273a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs @@ -6,7 +6,7 @@ fn check(ra_fixture: &str, expect: Expect) { let (db, file_id) = TestDB::with_single_file(ra_fixture); let item_tree = db.file_item_tree(file_id.into()); - let pretty = item_tree.pretty_print(); + let pretty = item_tree.pretty_print(&db); expect.assert_eq(&pretty); } @@ -165,7 +165,7 @@ trait Tr: SuperTrait + 'lifetime { fn method(&self); } "#, - expect![[r##" + expect![[r#" pub static mut ST: () = _; pub(self) const _: Anon = _; @@ -174,8 +174,8 @@ trait Tr: SuperTrait + 'lifetime { #[inner_attr_in_fn] pub(self) fn f( #[attr] - arg: u8, - _: (), + u8, + (), ) -> () { ... } pub(self) trait Tr @@ -186,10 +186,10 @@ pub(self) trait Tr pub(self) type Assoc: AssocBound = Default; pub(self) fn method( - _: &Self, // self + self: &Self, ) -> (); } - "##]], + "#]], ); } @@ -336,7 +336,7 @@ impl<'a, 'b, T, const K: u8> S::<'a, 'b, T, K> T: 'b { pub(self) fn f( - arg: impl Copy, + impl Copy, ) -> impl Copy where G: 'a { ... } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs index d338bb412054..0e9ac58fbaaa 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -2,14 +2,13 @@ //! //! This attribute to tell the compiler about semi built-in std library //! features, such as Fn family of traits. -use std::sync::Arc; - use rustc_hash::FxHashMap; use syntax::SmolStr; +use triomphe::Arc; use crate::{ - db::DefDatabase, AdtId, AssocItemId, AttrDefId, CrateId, EnumId, EnumVariantId, FunctionId, - ImplId, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId, + db::DefDatabase, path::Path, AdtId, AssocItemId, AttrDefId, CrateId, EnumId, EnumVariantId, + FunctionId, ImplId, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -200,7 +199,7 @@ pub enum GenericRequirement { macro_rules! language_item_table { ( - $( $(#[$attr:meta])* $variant:ident, $name:ident, $method:ident, $target:expr, $generics:expr; )* + $( $(#[$attr:meta])* $variant:ident, $module:ident :: $name:ident, $method:ident, $target:expr, $generics:expr; )* ) => { /// A representation of all the valid language items in Rust. @@ -220,11 +219,6 @@ pub fn name(self) -> SmolStr { } } - /// Opposite of [`LangItem::name`] - pub fn from_name(name: &hir_expand::name::Name) -> Option { - Self::from_str(name.as_str()?) - } - /// Opposite of [`LangItem::name`] pub fn from_str(name: &str) -> Option { match name { @@ -236,84 +230,100 @@ pub fn from_str(name: &str) -> Option { } } +impl LangItem { + /// Opposite of [`LangItem::name`] + pub fn from_name(name: &hir_expand::name::Name) -> Option { + Self::from_str(name.as_str()?) + } + + pub fn path(&self, db: &dyn DefDatabase, start_crate: CrateId) -> Option { + let t = db.lang_item(start_crate, *self)?; + Some(Path::LangItem(t)) + } +} + language_item_table! { // Variant name, Name, Getter method name, Target Generic requirements; - Sized, sized, sized_trait, Target::Trait, GenericRequirement::Exact(0); - Unsize, unsize, unsize_trait, Target::Trait, GenericRequirement::Minimum(1); + Sized, sym::sized, sized_trait, Target::Trait, GenericRequirement::Exact(0); + Unsize, sym::unsize, unsize_trait, Target::Trait, GenericRequirement::Minimum(1); /// Trait injected by `#[derive(PartialEq)]`, (i.e. "Partial EQ"). - StructuralPeq, structural_peq, structural_peq_trait, Target::Trait, GenericRequirement::None; + StructuralPeq, sym::structural_peq, structural_peq_trait, Target::Trait, GenericRequirement::None; /// Trait injected by `#[derive(Eq)]`, (i.e. "Total EQ"; no, I will not apologize). - StructuralTeq, structural_teq, structural_teq_trait, Target::Trait, GenericRequirement::None; - Copy, copy, copy_trait, Target::Trait, GenericRequirement::Exact(0); - Clone, clone, clone_trait, Target::Trait, GenericRequirement::None; - Sync, sync, sync_trait, Target::Trait, GenericRequirement::Exact(0); - DiscriminantKind, discriminant_kind, discriminant_kind_trait, Target::Trait, GenericRequirement::None; + StructuralTeq, sym::structural_teq, structural_teq_trait, Target::Trait, GenericRequirement::None; + Copy, sym::copy, copy_trait, Target::Trait, GenericRequirement::Exact(0); + Clone, sym::clone, clone_trait, Target::Trait, GenericRequirement::None; + Sync, sym::sync, sync_trait, Target::Trait, GenericRequirement::Exact(0); + DiscriminantKind, sym::discriminant_kind, discriminant_kind_trait, Target::Trait, GenericRequirement::None; /// The associated item of the [`DiscriminantKind`] trait. - Discriminant, discriminant_type, discriminant_type, Target::AssocTy, GenericRequirement::None; + Discriminant, sym::discriminant_type, discriminant_type, Target::AssocTy, GenericRequirement::None; - PointeeTrait, pointee_trait, pointee_trait, Target::Trait, GenericRequirement::None; - Metadata, metadata_type, metadata_type, Target::AssocTy, GenericRequirement::None; - DynMetadata, dyn_metadata, dyn_metadata, Target::Struct, GenericRequirement::None; + PointeeTrait, sym::pointee_trait, pointee_trait, Target::Trait, GenericRequirement::None; + Metadata, sym::metadata_type, metadata_type, Target::AssocTy, GenericRequirement::None; + DynMetadata, sym::dyn_metadata, dyn_metadata, Target::Struct, GenericRequirement::None; - Freeze, freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0); + Freeze, sym::freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0); - Drop, drop, drop_trait, Target::Trait, GenericRequirement::None; - Destruct, destruct, destruct_trait, Target::Trait, GenericRequirement::None; + FnPtrTrait, sym::fn_ptr_trait, fn_ptr_trait, Target::Trait, GenericRequirement::Exact(0); + FnPtrAddr, sym::fn_ptr_addr, fn_ptr_addr, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - CoerceUnsized, coerce_unsized, coerce_unsized_trait, Target::Trait, GenericRequirement::Minimum(1); - DispatchFromDyn, dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1); + Drop, sym::drop, drop_trait, Target::Trait, GenericRequirement::None; + Destruct, sym::destruct, destruct_trait, Target::Trait, GenericRequirement::None; + + CoerceUnsized, sym::coerce_unsized, coerce_unsized_trait, Target::Trait, GenericRequirement::Minimum(1); + DispatchFromDyn, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1); // language items relating to transmutability - TransmuteOpts, transmute_opts, transmute_opts, Target::Struct, GenericRequirement::Exact(0); - TransmuteTrait, transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(3); + TransmuteOpts, sym::transmute_opts, transmute_opts, Target::Struct, GenericRequirement::Exact(0); + TransmuteTrait, sym::transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(3); - Add, add, add_trait, Target::Trait, GenericRequirement::Exact(1); - Sub, sub, sub_trait, Target::Trait, GenericRequirement::Exact(1); - Mul, mul, mul_trait, Target::Trait, GenericRequirement::Exact(1); - Div, div, div_trait, Target::Trait, GenericRequirement::Exact(1); - Rem, rem, rem_trait, Target::Trait, GenericRequirement::Exact(1); - Neg, neg, neg_trait, Target::Trait, GenericRequirement::Exact(0); - Not, not, not_trait, Target::Trait, GenericRequirement::Exact(0); - BitXor, bitxor, bitxor_trait, Target::Trait, GenericRequirement::Exact(1); - BitAnd, bitand, bitand_trait, Target::Trait, GenericRequirement::Exact(1); - BitOr, bitor, bitor_trait, Target::Trait, GenericRequirement::Exact(1); - Shl, shl, shl_trait, Target::Trait, GenericRequirement::Exact(1); - Shr, shr, shr_trait, Target::Trait, GenericRequirement::Exact(1); - AddAssign, add_assign, add_assign_trait, Target::Trait, GenericRequirement::Exact(1); - SubAssign, sub_assign, sub_assign_trait, Target::Trait, GenericRequirement::Exact(1); - MulAssign, mul_assign, mul_assign_trait, Target::Trait, GenericRequirement::Exact(1); - DivAssign, div_assign, div_assign_trait, Target::Trait, GenericRequirement::Exact(1); - RemAssign, rem_assign, rem_assign_trait, Target::Trait, GenericRequirement::Exact(1); - BitXorAssign, bitxor_assign, bitxor_assign_trait, Target::Trait, GenericRequirement::Exact(1); - BitAndAssign, bitand_assign, bitand_assign_trait, Target::Trait, GenericRequirement::Exact(1); - BitOrAssign, bitor_assign, bitor_assign_trait, Target::Trait, GenericRequirement::Exact(1); - ShlAssign, shl_assign, shl_assign_trait, Target::Trait, GenericRequirement::Exact(1); - ShrAssign, shr_assign, shr_assign_trait, Target::Trait, GenericRequirement::Exact(1); - Index, index, index_trait, Target::Trait, GenericRequirement::Exact(1); - IndexMut, index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1); + Add, sym::add, add_trait, Target::Trait, GenericRequirement::Exact(1); + Sub, sym::sub, sub_trait, Target::Trait, GenericRequirement::Exact(1); + Mul, sym::mul, mul_trait, Target::Trait, GenericRequirement::Exact(1); + Div, sym::div, div_trait, Target::Trait, GenericRequirement::Exact(1); + Rem, sym::rem, rem_trait, Target::Trait, GenericRequirement::Exact(1); + Neg, sym::neg, neg_trait, Target::Trait, GenericRequirement::Exact(0); + Not, sym::not, not_trait, Target::Trait, GenericRequirement::Exact(0); + BitXor, sym::bitxor, bitxor_trait, Target::Trait, GenericRequirement::Exact(1); + BitAnd, sym::bitand, bitand_trait, Target::Trait, GenericRequirement::Exact(1); + BitOr, sym::bitor, bitor_trait, Target::Trait, GenericRequirement::Exact(1); + Shl, sym::shl, shl_trait, Target::Trait, GenericRequirement::Exact(1); + Shr, sym::shr, shr_trait, Target::Trait, GenericRequirement::Exact(1); + AddAssign, sym::add_assign, add_assign_trait, Target::Trait, GenericRequirement::Exact(1); + SubAssign, sym::sub_assign, sub_assign_trait, Target::Trait, GenericRequirement::Exact(1); + MulAssign, sym::mul_assign, mul_assign_trait, Target::Trait, GenericRequirement::Exact(1); + DivAssign, sym::div_assign, div_assign_trait, Target::Trait, GenericRequirement::Exact(1); + RemAssign, sym::rem_assign, rem_assign_trait, Target::Trait, GenericRequirement::Exact(1); + BitXorAssign, sym::bitxor_assign, bitxor_assign_trait, Target::Trait, GenericRequirement::Exact(1); + BitAndAssign, sym::bitand_assign, bitand_assign_trait, Target::Trait, GenericRequirement::Exact(1); + BitOrAssign, sym::bitor_assign, bitor_assign_trait, Target::Trait, GenericRequirement::Exact(1); + ShlAssign, sym::shl_assign, shl_assign_trait, Target::Trait, GenericRequirement::Exact(1); + ShrAssign, sym::shr_assign, shr_assign_trait, Target::Trait, GenericRequirement::Exact(1); + Index, sym::index, index_trait, Target::Trait, GenericRequirement::Exact(1); + IndexMut, sym::index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1); - UnsafeCell, unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None; - VaList, va_list, va_list, Target::Struct, GenericRequirement::None; + UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None; + VaList, sym::va_list, va_list, Target::Struct, GenericRequirement::None; - Deref, deref, deref_trait, Target::Trait, GenericRequirement::Exact(0); - DerefMut, deref_mut, deref_mut_trait, Target::Trait, GenericRequirement::Exact(0); - DerefTarget, deref_target, deref_target, Target::AssocTy, GenericRequirement::None; - Receiver, receiver, receiver_trait, Target::Trait, GenericRequirement::None; + Deref, sym::deref, deref_trait, Target::Trait, GenericRequirement::Exact(0); + DerefMut, sym::deref_mut, deref_mut_trait, Target::Trait, GenericRequirement::Exact(0); + DerefTarget, sym::deref_target, deref_target, Target::AssocTy, GenericRequirement::None; + Receiver, sym::receiver, receiver_trait, Target::Trait, GenericRequirement::None; - Fn, fn, fn_trait, Target::Trait, GenericRequirement::Exact(1); - FnMut, fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); - FnOnce, fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1); + Fn, kw::fn, fn_trait, Target::Trait, GenericRequirement::Exact(1); + FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); + FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1); - FnOnceOutput, fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None; + FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None; - Future, future_trait, future_trait, Target::Trait, GenericRequirement::Exact(0); - GeneratorState, generator_state, gen_state, Target::Enum, GenericRequirement::None; - Generator, generator, gen_trait, Target::Trait, GenericRequirement::Minimum(1); - Unpin, unpin, unpin_trait, Target::Trait, GenericRequirement::None; - Pin, pin, pin_type, Target::Struct, GenericRequirement::None; + Future, sym::future_trait, future_trait, Target::Trait, GenericRequirement::Exact(0); + GeneratorState, sym::generator_state, gen_state, Target::Enum, GenericRequirement::None; + Generator, sym::generator, gen_trait, Target::Trait, GenericRequirement::Minimum(1); + Unpin, sym::unpin, unpin_trait, Target::Trait, GenericRequirement::None; + Pin, sym::pin, pin_type, Target::Struct, GenericRequirement::None; - PartialEq, eq, eq_trait, Target::Trait, GenericRequirement::Exact(1); - PartialOrd, partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1); + PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1); + PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1); + CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None; // A number of panic-related lang items. The `panic` item corresponds to divide-by-zero and // various panic cases with `match`. The `panic_bounds_check` item is for indexing arrays. @@ -322,92 +332,103 @@ pub fn from_str(name: &str) -> Option { // in the sense that a crate is not required to have it defined to use it, but a final product // is required to define it somewhere. Additionally, there are restrictions on crates that use // a weak lang item, but do not have it defined. - Panic, panic, panic_fn, Target::Fn, GenericRequirement::Exact(0); - PanicNounwind, panic_nounwind, panic_nounwind, Target::Fn, GenericRequirement::Exact(0); - PanicFmt, panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None; - PanicDisplay, panic_display, panic_display, Target::Fn, GenericRequirement::None; - ConstPanicFmt, const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None; - PanicBoundsCheck, panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0); - PanicInfo, panic_info, panic_info, Target::Struct, GenericRequirement::None; - PanicLocation, panic_location, panic_location, Target::Struct, GenericRequirement::None; - PanicImpl, panic_impl, panic_impl, Target::Fn, GenericRequirement::None; - PanicCannotUnwind, panic_cannot_unwind, panic_cannot_unwind, Target::Fn, GenericRequirement::Exact(0); + Panic, sym::panic, panic_fn, Target::Fn, GenericRequirement::Exact(0); + PanicNounwind, sym::panic_nounwind, panic_nounwind, Target::Fn, GenericRequirement::Exact(0); + PanicFmt, sym::panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None; + PanicDisplay, sym::panic_display, panic_display, Target::Fn, GenericRequirement::None; + ConstPanicFmt, sym::const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None; + PanicBoundsCheck, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0); + PanicMisalignedPointerDereference, sym::panic_misaligned_pointer_dereference, panic_misaligned_pointer_dereference_fn, Target::Fn, GenericRequirement::Exact(0); + PanicInfo, sym::panic_info, panic_info, Target::Struct, GenericRequirement::None; + PanicLocation, sym::panic_location, panic_location, Target::Struct, GenericRequirement::None; + PanicImpl, sym::panic_impl, panic_impl, Target::Fn, GenericRequirement::None; + PanicCannotUnwind, sym::panic_cannot_unwind, panic_cannot_unwind, Target::Fn, GenericRequirement::Exact(0); /// libstd panic entry point. Necessary for const eval to be able to catch it - BeginPanic, begin_panic, begin_panic_fn, Target::Fn, GenericRequirement::None; + BeginPanic, sym::begin_panic, begin_panic_fn, Target::Fn, GenericRequirement::None; - ExchangeMalloc, exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None; - BoxFree, box_free, box_free_fn, Target::Fn, GenericRequirement::Minimum(1); - DropInPlace, drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1); - AllocLayout, alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None; + // Lang items needed for `format_args!()`. + FormatAlignment, sym::format_alignment, format_alignment, Target::Enum, GenericRequirement::None; + FormatArgument, sym::format_argument, format_argument, Target::Struct, GenericRequirement::None; + FormatArguments, sym::format_arguments, format_arguments, Target::Struct, GenericRequirement::None; + FormatCount, sym::format_count, format_count, Target::Enum, GenericRequirement::None; + FormatPlaceholder, sym::format_placeholder, format_placeholder, Target::Struct, GenericRequirement::None; + FormatUnsafeArg, sym::format_unsafe_arg, format_unsafe_arg, Target::Struct, GenericRequirement::None; - Start, start, start_fn, Target::Fn, GenericRequirement::Exact(1); + ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None; + BoxFree, sym::box_free, box_free_fn, Target::Fn, GenericRequirement::Minimum(1); + DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1); + AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None; - EhPersonality, eh_personality, eh_personality, Target::Fn, GenericRequirement::None; - EhCatchTypeinfo, eh_catch_typeinfo, eh_catch_typeinfo, Target::Static, GenericRequirement::None; + Start, sym::start, start_fn, Target::Fn, GenericRequirement::Exact(1); - OwnedBox, owned_box, owned_box, Target::Struct, GenericRequirement::Minimum(1); + EhPersonality, sym::eh_personality, eh_personality, Target::Fn, GenericRequirement::None; + EhCatchTypeinfo, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static, GenericRequirement::None; - PhantomData, phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1); + OwnedBox, sym::owned_box, owned_box, Target::Struct, GenericRequirement::Minimum(1); - ManuallyDrop, manually_drop, manually_drop, Target::Struct, GenericRequirement::None; + PhantomData, sym::phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1); - MaybeUninit, maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None; + ManuallyDrop, sym::manually_drop, manually_drop, Target::Struct, GenericRequirement::None; + + MaybeUninit, sym::maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None; /// Align offset for stride != 1; must not panic. - AlignOffset, align_offset, align_offset_fn, Target::Fn, GenericRequirement::None; + AlignOffset, sym::align_offset, align_offset_fn, Target::Fn, GenericRequirement::None; - Termination, termination, termination, Target::Trait, GenericRequirement::None; + Termination, sym::termination, termination, Target::Trait, GenericRequirement::None; - Try, Try, try_trait, Target::Trait, GenericRequirement::None; + Try, sym::Try, try_trait, Target::Trait, GenericRequirement::None; - Tuple, tuple_trait, tuple_trait, Target::Trait, GenericRequirement::Exact(0); + Tuple, sym::tuple_trait, tuple_trait, Target::Trait, GenericRequirement::Exact(0); - SliceLen, slice_len_fn, slice_len_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; + SliceLen, sym::slice_len_fn, slice_len_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; // Language items from AST lowering - TryTraitFromResidual, from_residual, from_residual_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - TryTraitFromOutput, from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - TryTraitBranch, branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - TryTraitFromYeet, from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None; + TryTraitFromResidual, sym::from_residual, from_residual_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + TryTraitFromOutput, sym::from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None; - PointerSized, pointer_sized, pointer_sized, Target::Trait, GenericRequirement::Exact(0); + PointerLike, sym::pointer_like, pointer_like, Target::Trait, GenericRequirement::Exact(0); - Poll, Poll, poll, Target::Enum, GenericRequirement::None; - PollReady, Ready, poll_ready_variant, Target::Variant, GenericRequirement::None; - PollPending, Pending, poll_pending_variant, Target::Variant, GenericRequirement::None; + ConstParamTy, sym::const_param_ty, const_param_ty_trait, Target::Trait, GenericRequirement::Exact(0); + + Poll, sym::Poll, poll, Target::Enum, GenericRequirement::None; + PollReady, sym::Ready, poll_ready_variant, Target::Variant, GenericRequirement::None; + PollPending, sym::Pending, poll_pending_variant, Target::Variant, GenericRequirement::None; // FIXME(swatinem): the following lang items are used for async lowering and // should become obsolete eventually. - ResumeTy, ResumeTy, resume_ty, Target::Struct, GenericRequirement::None; - GetContext, get_context, get_context_fn, Target::Fn, GenericRequirement::None; + ResumeTy, sym::ResumeTy, resume_ty, Target::Struct, GenericRequirement::None; + GetContext, sym::get_context, get_context_fn, Target::Fn, GenericRequirement::None; - Context, Context, context, Target::Struct, GenericRequirement::None; - FuturePoll, poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + Context, sym::Context, context, Target::Struct, GenericRequirement::None; + FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - FromFrom, from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + Option, sym::Option, option_type, Target::Enum, GenericRequirement::None; + OptionSome, sym::Some, option_some_variant, Target::Variant, GenericRequirement::None; + OptionNone, sym::None, option_none_variant, Target::Variant, GenericRequirement::None; - OptionSome, Some, option_some_variant, Target::Variant, GenericRequirement::None; - OptionNone, None, option_none_variant, Target::Variant, GenericRequirement::None; + ResultOk, sym::Ok, result_ok_variant, Target::Variant, GenericRequirement::None; + ResultErr, sym::Err, result_err_variant, Target::Variant, GenericRequirement::None; - ResultOk, Ok, result_ok_variant, Target::Variant, GenericRequirement::None; - ResultErr, Err, result_err_variant, Target::Variant, GenericRequirement::None; + ControlFlowContinue, sym::Continue, cf_continue_variant, Target::Variant, GenericRequirement::None; + ControlFlowBreak, sym::Break, cf_break_variant, Target::Variant, GenericRequirement::None; - ControlFlowContinue, Continue, cf_continue_variant, Target::Variant, GenericRequirement::None; - ControlFlowBreak, Break, cf_break_variant, Target::Variant, GenericRequirement::None; + IntoFutureIntoFuture, sym::into_future, into_future_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + IntoIterIntoIter, sym::into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None; - IntoFutureIntoFuture, into_future, into_future_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - IntoIterIntoIter, into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; - IteratorNext, next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None; + PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; - PinNewUnchecked, new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; + RangeFrom, sym::RangeFrom, range_from_struct, Target::Struct, GenericRequirement::None; + RangeFull, sym::RangeFull, range_full_struct, Target::Struct, GenericRequirement::None; + RangeInclusiveStruct, sym::RangeInclusive, range_inclusive_struct, Target::Struct, GenericRequirement::None; + RangeInclusiveNew, sym::range_inclusive_new, range_inclusive_new_method, Target::Method(MethodKind::Inherent), GenericRequirement::None; + Range, sym::Range, range_struct, Target::Struct, GenericRequirement::None; + RangeToInclusive, sym::RangeToInclusive, range_to_inclusive_struct, Target::Struct, GenericRequirement::None; + RangeTo, sym::RangeTo, range_to_struct, Target::Struct, GenericRequirement::None; - RangeFrom, RangeFrom, range_from_struct, Target::Struct, GenericRequirement::None; - RangeFull, RangeFull, range_full_struct, Target::Struct, GenericRequirement::None; - RangeInclusiveStruct, RangeInclusive, range_inclusive_struct, Target::Struct, GenericRequirement::None; - RangeInclusiveNew, range_inclusive_new, range_inclusive_new_method, Target::Method(MethodKind::Inherent), GenericRequirement::None; - Range, Range, range_struct, Target::Struct, GenericRequirement::None; - RangeToInclusive, RangeToInclusive, range_to_inclusive_struct, Target::Struct, GenericRequirement::None; - RangeTo, RangeTo, range_to_struct, Target::Struct, GenericRequirement::None; - - String, String, string, Target::Struct, GenericRequirement::None; + String, sym::String, string, Target::Struct, GenericRequirement::None; + CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None; } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/layout.rs b/src/tools/rust-analyzer/crates/hir-def/src/layout.rs deleted file mode 100644 index 49b1190ad46a..000000000000 --- a/src/tools/rust-analyzer/crates/hir-def/src/layout.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Definitions needed for computing data layout of types. - -use std::cmp; - -use la_arena::{Idx, RawIdx}; -pub use rustc_abi::{ - Abi, AbiAndPrefAlign, AddressSpace, Align, Endian, FieldsShape, Integer, IntegerType, - LayoutCalculator, Niche, Primitive, ReprFlags, ReprOptions, Scalar, Size, StructKind, - TargetDataLayout, TargetDataLayoutErrors, WrappingRange, -}; - -use crate::LocalEnumVariantId; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct RustcEnumVariantIdx(pub LocalEnumVariantId); - -impl rustc_index::vec::Idx for RustcEnumVariantIdx { - fn new(idx: usize) -> Self { - RustcEnumVariantIdx(Idx::from_raw(RawIdx::from(idx as u32))) - } - - fn index(self) -> usize { - u32::from(self.0.into_raw()) as usize - } -} - -pub type Layout = rustc_abi::LayoutS; -pub type TagEncoding = rustc_abi::TagEncoding; -pub type Variants = rustc_abi::Variants; - -pub trait IntegerExt { - fn repr_discr( - dl: &TargetDataLayout, - repr: &ReprOptions, - min: i128, - max: i128, - ) -> Result<(Integer, bool), LayoutError>; -} - -impl IntegerExt for Integer { - /// Finds the appropriate Integer type and signedness for the given - /// signed discriminant range and `#[repr]` attribute. - /// N.B.: `u128` values above `i128::MAX` will be treated as signed, but - /// that shouldn't affect anything, other than maybe debuginfo. - fn repr_discr( - dl: &TargetDataLayout, - repr: &ReprOptions, - min: i128, - max: i128, - ) -> Result<(Integer, bool), LayoutError> { - // Theoretically, negative values could be larger in unsigned representation - // than the unsigned representation of the signed minimum. However, if there - // are any negative values, the only valid unsigned representation is u128 - // which can fit all i128 values, so the result remains unaffected. - let unsigned_fit = Integer::fit_unsigned(cmp::max(min as u128, max as u128)); - let signed_fit = cmp::max(Integer::fit_signed(min), Integer::fit_signed(max)); - - if let Some(ity) = repr.int { - let discr = Integer::from_attr(dl, ity); - let fit = if ity.is_signed() { signed_fit } else { unsigned_fit }; - if discr < fit { - return Err(LayoutError::UserError( - "Integer::repr_discr: `#[repr]` hint too small for \ - discriminant range of enum " - .to_string(), - )); - } - return Ok((discr, ity.is_signed())); - } - - let at_least = if repr.c() { - // This is usually I32, however it can be different on some platforms, - // notably hexagon and arm-none/thumb-none - dl.c_enum_min_size - } else { - // repr(Rust) enums try to be as small as possible - Integer::I8 - }; - - // If there are no negative values, we can use the unsigned fit. - Ok(if min >= 0 { - (cmp::max(unsigned_fit, at_least), false) - } else { - (cmp::max(signed_fit, at_least), true) - }) - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum LayoutError { - UserError(String), - SizeOverflow, - TargetLayoutNotAvailable, - HasPlaceholder, - NotImplemented, - Unknown, -} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index 8c2e93f09059..9cd3dfd6f7c9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -18,24 +18,23 @@ macro_rules! eprintln { pub mod attr; pub mod path; -pub mod type_ref; pub mod builtin_type; -pub mod builtin_attr; pub mod per_ns; pub mod item_scope; +pub mod lower; +pub mod expander; + pub mod dyn_map; -pub mod keys; pub mod item_tree; -pub mod adt; pub mod data; pub mod generics; pub mod lang_item; -pub mod layout; -pub mod expr; +pub mod hir; +pub use self::hir::type_ref; pub mod body; pub mod resolver; @@ -49,29 +48,34 @@ macro_rules! eprintln { pub mod find_path; pub mod import_map; +pub use rustc_abi as layout; +use triomphe::Arc; + #[cfg(test)] mod test_db; #[cfg(test)] mod macro_expansion_tests; mod pretty; -use std::{ - hash::{Hash, Hasher}, - sync::Arc, -}; +use std::hash::{Hash, Hasher}; -use base_db::{impl_intern_key, salsa, CrateId, ProcMacroKind}; +use base_db::{ + impl_intern_key, + salsa::{self, InternId}, + CrateId, ProcMacroKind, +}; use hir_expand::{ ast_id_map::FileAstId, attrs::{Attr, AttrId, AttrInput}, builtin_attr_macro::BuiltinAttrExpander, builtin_derive_macro::BuiltinDeriveExpander, builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, - eager::{expand_eager_macro, ErrorEmitted, ErrorSink}, + db::ExpandDatabase, + eager::expand_eager_macro, hygiene::Hygiene, proc_macro::ProcMacroExpander, - AstId, ExpandError, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, - MacroDefKind, UnresolvedMacro, + AstId, ExpandError, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, + MacroDefId, MacroDefKind, UnresolvedMacro, }; use item_tree::ExternBlock; use la_arena::Idx; @@ -82,8 +86,8 @@ macro_rules! eprintln { use ::tt::token_id as tt; use crate::{ - adt::VariantData, builtin_type::BuiltinType, + data::adt::VariantData, item_tree::{ Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, ModItem, Static, Struct, Trait, TraitAlias, TypeAlias, Union, @@ -104,13 +108,7 @@ pub struct ModuleId { impl ModuleId { pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc { match self.block { - Some(block) => { - db.block_def_map(block).unwrap_or_else(|| { - // NOTE: This should be unreachable - all `ModuleId`s come from their `DefMap`s, - // so the `DefMap` here must exist. - unreachable!("no `block_def_map` for `ModuleId` {:?}", self); - }) - } + Some(block) => db.block_def_map(block), None => db.crate_def_map(self.krate), } } @@ -236,7 +234,7 @@ pub struct EnumVariantId { pub local_id: LocalEnumVariantId, } -pub type LocalEnumVariantId = Idx; +pub type LocalEnumVariantId = Idx; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FieldId { @@ -244,7 +242,7 @@ pub struct FieldId { pub local_id: LocalFieldId, } -pub type LocalFieldId = Idx; +pub type LocalFieldId = Idx; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ConstId(salsa::InternId); @@ -478,6 +476,46 @@ pub enum ModuleDefId { for ModuleDefId ); +// FIXME: make this a DefWithBodyId +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub struct AnonymousConstId(InternId); +impl_intern_key!(AnonymousConstId); + +/// A constant, which might appears as a const item, an annonymous const block in expressions +/// or patterns, or as a constant in types with const generics. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GeneralConstId { + ConstId(ConstId), + AnonymousConstId(AnonymousConstId), +} + +impl_from!(ConstId, AnonymousConstId for GeneralConstId); + +impl GeneralConstId { + pub fn generic_def(self, db: &dyn db::DefDatabase) -> Option { + match self { + GeneralConstId::ConstId(x) => Some(x.into()), + GeneralConstId::AnonymousConstId(x) => { + let (parent, _) = db.lookup_intern_anonymous_const(x); + parent.as_generic_def_id() + } + } + } + + pub fn name(self, db: &dyn db::DefDatabase) -> String { + match self { + GeneralConstId::ConstId(const_id) => db + .const_data(const_id) + .name + .as_ref() + .and_then(|x| x.as_str()) + .unwrap_or("_") + .to_owned(), + GeneralConstId::AnonymousConstId(id) => format!("{{anonymous const {id:?}}}"), + } + } +} + /// The defs which have a body. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DefWithBodyId { @@ -799,52 +837,43 @@ pub fn krate(&self, db: &dyn db::DefDatabase) -> CrateId { pub trait AsMacroCall { fn as_call_id( &self, - db: &dyn db::DefDatabase, + db: &dyn ExpandDatabase, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, ) -> Option { - self.as_call_id_with_errors(db, krate, resolver, &mut |_| ()).ok()?.ok() + self.as_call_id_with_errors(db, krate, resolver).ok()?.value } fn as_call_id_with_errors( &self, - db: &dyn db::DefDatabase, + db: &dyn ExpandDatabase, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, - error_sink: &mut dyn FnMut(ExpandError), - ) -> Result, UnresolvedMacro>; + ) -> Result>, UnresolvedMacro>; } impl AsMacroCall for InFile<&ast::MacroCall> { fn as_call_id_with_errors( &self, - db: &dyn db::DefDatabase, + db: &dyn ExpandDatabase, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, - mut error_sink: &mut dyn FnMut(ExpandError), - ) -> Result, UnresolvedMacro> { + ) -> Result>, UnresolvedMacro> { let expands_to = hir_expand::ExpandTo::from_call_site(self.value); let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); - let h = Hygiene::new(db.upcast(), self.file_id); - let path = - self.value.path().and_then(|path| path::ModPath::from_src(db.upcast(), path, &h)); + let h = Hygiene::new(db, self.file_id); + let path = self.value.path().and_then(|path| path::ModPath::from_src(db, path, &h)); - let path = match error_sink - .option(path, || ExpandError::Other("malformed macro invocation".into())) - { - Ok(path) => path, - Err(error) => { - return Ok(Err(error)); - } + let Some(path) = path else { + return Ok(ExpandResult::only_err(ExpandError::Other("malformed macro invocation".into()))); }; - macro_call_as_call_id( + macro_call_as_call_id_( db, &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), expands_to, krate, resolver, - error_sink, ) } } @@ -863,26 +892,37 @@ fn new(file_id: HirFileId, ast_id: FileAstId, path: path::ModPath) -> AstIdWi } fn macro_call_as_call_id( - db: &dyn db::DefDatabase, + db: &dyn ExpandDatabase, call: &AstIdWithPath, expand_to: ExpandTo, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, - error_sink: &mut dyn FnMut(ExpandError), -) -> Result, UnresolvedMacro> { +) -> Result, UnresolvedMacro> { + macro_call_as_call_id_(db, call, expand_to, krate, resolver).map(|res| res.value) +} + +fn macro_call_as_call_id_( + db: &dyn ExpandDatabase, + call: &AstIdWithPath, + expand_to: ExpandTo, + krate: CrateId, + resolver: impl Fn(path::ModPath) -> Option, +) -> Result>, UnresolvedMacro> { let def = resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?; let res = if let MacroDefKind::BuiltInEager(..) = def.kind { - let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast())); - - expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver, error_sink)? + let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db)); + expand_eager_macro(db, krate, macro_call, def, &resolver)? } else { - Ok(def.as_lazy_macro( - db.upcast(), - krate, - MacroCallKind::FnLike { ast_id: call.ast_id, expand_to }, - )) + ExpandResult { + value: Some(def.as_lazy_macro( + db, + krate, + MacroCallKind::FnLike { ast_id: call.ast_id, expand_to }, + )), + err: None, + } }; Ok(res) } @@ -986,7 +1026,6 @@ fn attr_macro_as_call_id( macro_attr: &Attr, krate: CrateId, def: MacroDefId, - is_derive: bool, ) -> MacroCallId { let arg = match macro_attr.input.as_deref() { Some(AttrInput::TokenTree(tt, map)) => ( @@ -1007,7 +1046,6 @@ fn attr_macro_as_call_id( ast_id: item_attr.ast_id, attr_args: Arc::new(arg), invoc_attr_index: macro_attr.id, - is_derive, }, ) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/lower.rs new file mode 100644 index 000000000000..af623fd0e5d4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/lower.rs @@ -0,0 +1,45 @@ +//! Context for lowering paths. +use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile}; +use once_cell::unsync::OnceCell; +use syntax::ast; +use triomphe::Arc; + +use crate::{db::DefDatabase, path::Path}; + +pub struct LowerCtx<'a> { + pub db: &'a dyn DefDatabase, + hygiene: Hygiene, + ast_id_map: Option<(HirFileId, OnceCell>)>, +} + +impl<'a> LowerCtx<'a> { + pub fn new(db: &'a dyn DefDatabase, hygiene: &Hygiene, file_id: HirFileId) -> Self { + LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: Some((file_id, OnceCell::new())) } + } + + pub fn with_file_id(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self { + LowerCtx { + db, + hygiene: Hygiene::new(db.upcast(), file_id), + ast_id_map: Some((file_id, OnceCell::new())), + } + } + + pub fn with_hygiene(db: &'a dyn DefDatabase, hygiene: &Hygiene) -> Self { + LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: None } + } + + pub(crate) fn hygiene(&self) -> &Hygiene { + &self.hygiene + } + + pub(crate) fn lower_path(&self, ast: ast::Path) -> Option { + Path::from_src(ast, self) + } + + pub(crate) fn ast_id(&self, item: &N) -> Option> { + let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?; + let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id)); + Some(InFile::new(file_id, ast_id_map.ast_id(item))) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs index fafcde25ae70..80474bc154d0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs @@ -16,7 +16,7 @@ fn test_copy_expand_simple() { #[derive(Copy)] struct Foo; -impl < > core::marker::Copy for Foo< > {}"#]], +impl < > core::marker::Copy for Foo< > where {}"#]], ); } @@ -41,7 +41,7 @@ fn test_copy_expand_in_core() { #[derive(Copy)] struct Foo; -impl < > crate ::marker::Copy for Foo< > {}"#]], +impl < > crate ::marker::Copy for Foo< > where {}"#]], ); } @@ -57,7 +57,7 @@ fn test_copy_expand_with_type_params() { #[derive(Copy)] struct Foo; -impl core::marker::Copy for Foo {}"#]], +impl core::marker::Copy for Foo where {}"#]], ); } @@ -74,7 +74,7 @@ fn test_copy_expand_with_lifetimes() { #[derive(Copy)] struct Foo; -impl core::marker::Copy for Foo {}"#]], +impl core::marker::Copy for Foo where {}"#]], ); } @@ -84,13 +84,33 @@ fn test_clone_expand() { r#" //- minicore: derive, clone #[derive(Clone)] -struct Foo; +enum Command { + Move { x: A, y: B }, + Do(&'static str), + Jump, +} "#, expect![[r#" #[derive(Clone)] -struct Foo; +enum Command { + Move { x: A, y: B }, + Do(&'static str), + Jump, +} -impl core::clone::Clone for Foo {}"#]], +impl core::clone::Clone for Command where { + fn clone(&self ) -> Self { + match self { + Command::Move { + x: x, y: y, + } + =>Command::Move { + x: x.clone(), y: y.clone(), + } + , Command::Do(f0, )=>Command::Do(f0.clone(), ), Command::Jump=>Command::Jump, + } + } +}"#]], ); } @@ -106,6 +126,270 @@ fn test_clone_expand_with_const_generics() { #[derive(Clone)] struct Foo(u32); -impl core::clone::Clone for Foo {}"#]], +impl core::clone::Clone for Foo where { + fn clone(&self ) -> Self { + match self { + Foo(f0, )=>Foo(f0.clone(), ), + } + } +}"#]], + ); +} + +#[test] +fn test_default_expand() { + check( + r#" +//- minicore: derive, default +#[derive(Default)] +struct Foo { + field1: i32, + field2: (), +} +#[derive(Default)] +enum Bar { + Foo(u8), + #[default] + Bar, +} +"#, + expect![[r#" +#[derive(Default)] +struct Foo { + field1: i32, + field2: (), +} +#[derive(Default)] +enum Bar { + Foo(u8), + #[default] + Bar, +} + +impl < > core::default::Default for Foo< > where { + fn default() -> Self { + Foo { + field1: core::default::Default::default(), field2: core::default::Default::default(), + } + } +} +impl < > core::default::Default for Bar< > where { + fn default() -> Self { + Bar::Bar + } +}"#]], + ); +} + +#[test] +fn test_partial_eq_expand() { + check( + r#" +//- minicore: derive, eq +#[derive(PartialEq, Eq)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +#[derive(PartialEq, Eq)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl < > core::cmp::PartialEq for Command< > where { + fn eq(&self , other: &Self ) -> bool { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false + } + } +} +impl < > core::cmp::Eq for Command< > where {}"#]], + ); +} + +#[test] +fn test_partial_ord_expand() { + check( + r#" +//- minicore: derive, ord +#[derive(PartialOrd, Ord)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +#[derive(PartialOrd, Ord)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl < > core::cmp::PartialOrd for Command< > where { + fn partial_cmp(&self , other: &Self ) -> core::option::Option::Option { + match core::intrinsics::discriminant_value(self ).partial_cmp(&core::intrinsics::discriminant_value(other)) { + core::option::Option::Some(core::cmp::Ordering::Equal)=> { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>match x_self.partial_cmp(&x_other) { + core::option::Option::Some(core::cmp::Ordering::Equal)=> { + match y_self.partial_cmp(&y_other) { + core::option::Option::Some(core::cmp::Ordering::Equal)=> { + core::option::Option::Some(core::cmp::Ordering::Equal) + } + c=>return c, + } + } + c=>return c, + } + , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) { + core::option::Option::Some(core::cmp::Ordering::Equal)=> { + core::option::Option::Some(core::cmp::Ordering::Equal) + } + c=>return c, + } + , (Command::Jump, Command::Jump)=>core::option::Option::Some(core::cmp::Ordering::Equal), _unused=>core::option::Option::Some(core::cmp::Ordering::Equal) + } + } + c=>return c, + } + } +} +impl < > core::cmp::Ord for Command< > where { + fn cmp(&self , other: &Self ) -> core::cmp::Ordering { + match core::intrinsics::discriminant_value(self ).cmp(&core::intrinsics::discriminant_value(other)) { + core::cmp::Ordering::Equal=> { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>match x_self.cmp(&x_other) { + core::cmp::Ordering::Equal=> { + match y_self.cmp(&y_other) { + core::cmp::Ordering::Equal=> { + core::cmp::Ordering::Equal + } + c=>return c, + } + } + c=>return c, + } + , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) { + core::cmp::Ordering::Equal=> { + core::cmp::Ordering::Equal + } + c=>return c, + } + , (Command::Jump, Command::Jump)=>core::cmp::Ordering::Equal, _unused=>core::cmp::Ordering::Equal + } + } + c=>return c, + } + } +}"#]], + ); +} + +#[test] +fn test_hash_expand() { + check( + r#" +//- minicore: derive, hash +use core::hash::Hash; + +#[derive(Hash)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +use core::hash::Hash; + +#[derive(Hash)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl < > core::hash::Hash for Command< > where { + fn hash(&self , state: &mut H) { + core::mem::discriminant(self ).hash(state); + match self { + Command::Move { + x: x, y: y, + } + => { + x.hash(state); + y.hash(state); + } + , Command::Do(f0, )=> { + f0.hash(state); + } + , Command::Jump=> {} + , + } + } +}"#]], + ); +} + +#[test] +fn test_debug_expand() { + check( + r#" +//- minicore: derive, fmt +use core::fmt::Debug; + +#[derive(Debug)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +use core::fmt::Debug; + +#[derive(Debug)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl < > core::fmt::Debug for Command< > where { + fn fmt(&self , f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Command::Move { + x: x, y: y, + } + =>f.debug_struct("Move").field("x", &x).field("y", &y).finish(), Command::Do(f0, )=>f.debug_tuple("Do").field(&f0).finish(), Command::Jump=>f.write_str("Jump"), + } + } +}"#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index 5fbd1789b3a9..977f300636f5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -13,12 +13,12 @@ macro_rules! column {() => {}} fn main() { column!(); } "#, - expect![[r##" + expect![[r#" #[rustc_builtin_macro] macro_rules! column {() => {}} -fn main() { 0; } -"##]], +fn main() { 0 as u32; } +"#]], ); } @@ -31,12 +31,12 @@ macro_rules! line {() => {}} fn main() { line!() } "#, - expect![[r##" + expect![[r#" #[rustc_builtin_macro] macro_rules! line {() => {}} -fn main() { 0 } -"##]], +fn main() { 0 as u32 } +"#]], ); } @@ -97,7 +97,7 @@ macro_rules! option_env {() => {}} #[rustc_builtin_macro] macro_rules! option_env {() => {}} -fn main() { $crate::option::Option::None:: < &str>; } +fn main() { ::core::option::Option::None:: < &str>; } "#]], ); } @@ -193,7 +193,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 */ }); @@ -201,9 +201,9 @@ macro_rules! format_args { } fn main() { - $crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::Argument::new(&(arg1(a, b, c)), $crate::fmt::Display::fmt), $crate::fmt::Argument::new(&(arg2), $crate::fmt::Display::fmt), ]); + ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::Argument::new(&(arg1(a, b, c)), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(arg2), ::core::fmt::Debug::fmt), ]); } -"#]], +"##]], ); } @@ -221,7 +221,7 @@ fn main() { format_args!("{} {:?}", a::(), b); } "#, - expect![[r#" + expect![[r##" #[rustc_builtin_macro] macro_rules! format_args { ($fmt:expr) => ({ /* compiler built-in */ }); @@ -229,9 +229,76 @@ macro_rules! format_args { } fn main() { - $crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::Argument::new(&(a::()), $crate::fmt::Display::fmt), $crate::fmt::Argument::new(&(b), $crate::fmt::Display::fmt), ]); + ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::Argument::new(&(a::()), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(b), ::core::fmt::Debug::fmt), ]); } -"#]], +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_raw_strings() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!( + r#"{},mismatch,"{}","{}""#, + location_csv_pat(db, &analysis, vfs, &sm, pat_id), + mismatch.expected.display(db), + mismatch.actual.display(db) + ); +} +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + ::core::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[::core::fmt::Argument::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(mismatch.expected.display(db)), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(mismatch.actual.display(db)), ::core::fmt::Display::fmt), ]); +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_eager() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! concat {} + +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!(concat!("xxx{}y", "{:?}zzz"), 2, b); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + ::core::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[::core::fmt::Argument::new(&(2), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(b), ::core::fmt::Debug::fmt), ]); +} +"##]], ); } @@ -250,7 +317,7 @@ fn main() { format_args!/*+errors*/("{} {:?}", a.); } "#, - expect![[r#" + expect![[r##" #[rustc_builtin_macro] macro_rules! format_args { ($fmt:expr) => ({ /* compiler built-in */ }); @@ -259,10 +326,10 @@ macro_rules! format_args { fn main() { let _ = - /* parse error: expected field name or number */ -$crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::Argument::new(&(a.), $crate::fmt::Display::fmt), ]); + /* error: no rule matches input tokens *//* parse error: expected field name or number */ +::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::Argument::new(&(a.), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(), ::core::fmt::Debug::fmt), ]); } -"#]], +"##]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index 7a3e8c3b05c9..553ffe3d0b88 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -4,6 +4,7 @@ mod tt_conversion; mod matching; mod meta_syntax; +mod metavar_expr; mod regression; use expect_test::expect; @@ -98,7 +99,7 @@ macro_rules! f {#0 ); } #[test] -fn float_field_acces_macro_input() { +fn float_field_access_macro_input() { check( r#" macro_rules! foo { @@ -922,7 +923,7 @@ macro_rules! m { fn bar() -> &'a Baz {} -fn bar() -> extern "Rust"fn() -> Ret {} +fn bar() -> extern "Rust" fn() -> Ret {} "#]], ); } @@ -1293,19 +1294,53 @@ fn test_underscore_flavors() { } #[test] -fn test_vertical_bar_with_pat() { +fn test_vertical_bar_with_pat_param() { check( r#" -macro_rules! m { (|$pat:pat| ) => { ok!(); } } +macro_rules! m { (|$pat:pat_param| ) => { ok!(); } } m! { |x| } "#, expect![[r#" -macro_rules! m { (|$pat:pat| ) => { ok!(); } } +macro_rules! m { (|$pat:pat_param| ) => { ok!(); } } ok!(); "#]], ); } +#[test] +fn test_new_std_matches() { + check( + r#" +macro_rules! matches { + ($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => { + match $expression { + $pattern $(if $guard)? => true, + _ => false + } + }; +} +fn main() { + matches!(0, 0 | 1 if true); +} + "#, + expect![[r#" +macro_rules! matches { + ($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => { + match $expression { + $pattern $(if $guard)? => true, + _ => false + } + }; +} +fn main() { + match 0 { + 0|1 if true =>true , _=>false + }; +} + "#]], + ); +} + #[test] fn test_dollar_crate_lhs_is_not_meta() { check( @@ -1580,92 +1615,6 @@ macro_rules! error { ) } -#[test] -fn test_dollar_dollar() { - check( - r#" -macro_rules! register_struct { ($Struct:ident) => { - macro_rules! register_methods { ($$($method:ident),*) => { - macro_rules! implement_methods { ($$$$($$val:expr),*) => { - struct $Struct; - impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*} - }} - }} -}} - -register_struct!(Foo); -register_methods!(alpha, beta); -implement_methods!(1, 2, 3); -"#, - expect![[r#" -macro_rules! register_struct { ($Struct:ident) => { - macro_rules! register_methods { ($$($method:ident),*) => { - macro_rules! implement_methods { ($$$$($$val:expr),*) => { - struct $Struct; - impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*} - }} - }} -}} - -macro_rules !register_methods { - ($($method: ident), *) = > { - macro_rules!implement_methods { - ($$($val: expr), *) = > { - struct Foo; - impl Foo { - $(fn $method()-> &'static[u32] { - &[$$($$val), *] - } - )* - } - } - } - } -} -macro_rules !implement_methods { - ($($val: expr), *) = > { - struct Foo; - impl Foo { - fn alpha()-> &'static[u32] { - &[$($val), *] - } - fn beta()-> &'static[u32] { - &[$($val), *] - } - } - } -} -struct Foo; -impl Foo { - fn alpha() -> &'static[u32] { - &[1, 2, 3] - } - fn beta() -> &'static[u32] { - &[1, 2, 3] - } -} -"#]], - ) -} - -#[test] -fn test_metavar_exprs() { - check( - r#" -macro_rules! m { - ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); -} -const _: i32 = m!(a b c); - "#, - expect![[r#" -macro_rules! m { - ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); -} -const _: i32 = -0--1--2; - "#]], - ); -} - #[test] fn test_punct_without_space() { // Puncts are "glued" greedily. diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs index 26f16542cbb2..0909d8c83544 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs @@ -33,7 +33,7 @@ fn test_expand_bad_literal() { "#, expect![[r#" macro_rules! m { ($i:literal) => {}; } -/* error: Failed to lower macro args to token tree */"#]], +/* error: invalid token tree */"#]], ); } @@ -73,7 +73,7 @@ fn main() { macro_rules! asi { ($($stmt:stmt)*) => ($($stmt)*); } fn main() { - let a = 2let b = 5drop(b-a)println!("{}", a+b) + let a = 2 let b = 5 drop(b-a)println!("{}", a+b) } "#]], ) @@ -106,7 +106,6 @@ macro_rules! m { #[test] fn range_patterns() { - // FIXME: rustc thinks there are three patterns here, not one. check( r#" macro_rules! m { @@ -118,7 +117,7 @@ macro_rules! m { macro_rules! m { ($($p:pat)*) => (stringify!($($p |)*);) } -stringify!(.. .. .. |); +stringify!(.. | .. | .. |); "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs new file mode 100644 index 000000000000..967b5ad36bab --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs @@ -0,0 +1,311 @@ +//! Tests for RFC 3086 metavariable expressions. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_dollar_dollar() { + check( + r#" +macro_rules! register_struct { ($Struct:ident) => { + macro_rules! register_methods { ($$($method:ident),*) => { + macro_rules! implement_methods { ($$$$($$val:expr),*) => { + struct $Struct; + impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*} + }} + }} +}} + +register_struct!(Foo); +register_methods!(alpha, beta); +implement_methods!(1, 2, 3); +"#, + expect![[r#" +macro_rules! register_struct { ($Struct:ident) => { + macro_rules! register_methods { ($$($method:ident),*) => { + macro_rules! implement_methods { ($$$$($$val:expr),*) => { + struct $Struct; + impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*} + }} + }} +}} + +macro_rules !register_methods { + ($($method: ident), *) = > { + macro_rules!implement_methods { + ($$($val: expr), *) = > { + struct Foo; + impl Foo { + $(fn $method()-> &'static[u32] { + &[$$($$val), *] + } + )* + } + } + } + } +} +macro_rules !implement_methods { + ($($val: expr), *) = > { + struct Foo; + impl Foo { + fn alpha()-> &'static[u32] { + &[$($val), *] + } + fn beta()-> &'static[u32] { + &[$($val), *] + } + } + } +} +struct Foo; +impl Foo { + fn alpha() -> &'static[u32] { + &[1, 2, 3] + } + fn beta() -> &'static[u32] { + &[1, 2, 3] + } +} +"#]], + ) +} + +#[test] +fn test_metavar_exprs() { + check( + r#" +macro_rules! m { + ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); +} +const _: i32 = m!(a b c); + "#, + expect![[r#" +macro_rules! m { + ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); +} +const _: i32 = -0--1--2; + "#]], + ); +} + +#[test] +fn count_basic() { + check( + r#" +macro_rules! m { + ($($t:ident),*) => { + ${count(t)} + } +} + +fn test() { + m!(); + m!(a); + m!(a, a); +} +"#, + expect![[r#" +macro_rules! m { + ($($t:ident),*) => { + ${count(t)} + } +} + +fn test() { + 0; + 1; + 2; +} +"#]], + ); +} + +#[test] +fn count_with_depth() { + check( + r#" +macro_rules! foo { + ($( $( $($t:ident)* ),* );*) => { + $( + { + let depth_none = ${count(t)}; + let depth_zero = ${count(t, 0)}; + let depth_one = ${count(t, 1)}; + } + )* + } +} + +fn bar() { + foo!( + a a a, a, a a; + a a a + ) +} +"#, + expect![[r#" +macro_rules! foo { + ($( $( $($t:ident)* ),* );*) => { + $( + { + let depth_none = ${count(t)}; + let depth_zero = ${count(t, 0)}; + let depth_one = ${count(t, 1)}; + } + )* + } +} + +fn bar() { + { + let depth_none = 6; + let depth_zero = 3; + let depth_one = 6; + } { + let depth_none = 3; + let depth_zero = 1; + let depth_one = 3; + } +} +"#]], + ); +} + +#[test] +fn count_depth_out_of_bounds() { + check( + r#" +macro_rules! foo { + ($($t:ident)*) => { ${count(t, 1)} }; + ($( $( $l:literal )* );*) => { $(${count(l, 1)};)* } +} +macro_rules! bar { + ($($t:ident)*) => { ${count(t, 1024)} }; + ($( $( $l:literal )* );*) => { $(${count(l, 8192)};)* } +} + +fn test() { + foo!(a b); + foo!(1 2; 3); + bar!(a b); + bar!(1 2; 3); +} +"#, + expect![[r#" +macro_rules! foo { + ($($t:ident)*) => { ${count(t, 1)} }; + ($( $( $l:literal )* );*) => { $(${count(l, 1)};)* } +} +macro_rules! bar { + ($($t:ident)*) => { ${count(t, 1024)} }; + ($( $( $l:literal )* );*) => { $(${count(l, 8192)};)* } +} + +fn test() { + /* error: ${count} out of bounds */; + /* error: ${count} out of bounds */; + /* error: ${count} out of bounds */; + /* error: ${count} out of bounds */; +} +"#]], + ); +} + +#[test] +fn misplaced_count() { + check( + r#" +macro_rules! foo { + ($($t:ident)*) => { $(${count(t)})* }; + ($l:literal) => { ${count(l)} } +} + +fn test() { + foo!(a b c); + foo!(1); +} +"#, + expect![[r#" +macro_rules! foo { + ($($t:ident)*) => { $(${count(t)})* }; + ($l:literal) => { ${count(l)} } +} + +fn test() { + /* error: ${count} misplaced */; + /* error: ${count} misplaced */; +} +"#]], + ); +} + +#[test] +fn malformed_count() { + check( + r#" +macro_rules! too_many_args { + ($($t:ident)*) => { ${count(t, 1, leftover)} } +} +macro_rules! depth_suffixed { + ($($t:ident)*) => { ${count(t, 0usize)} } +} +macro_rules! depth_too_large { + ($($t:ident)*) => { ${count(t, 18446744073709551616)} } +} + +fn test() { + too_many_args!(); + depth_suffixed!(); + depth_too_large!(); +} +"#, + expect![[r#" +macro_rules! too_many_args { + ($($t:ident)*) => { ${count(t, 1, leftover)} } +} +macro_rules! depth_suffixed { + ($($t:ident)*) => { ${count(t, 0usize)} } +} +macro_rules! depth_too_large { + ($($t:ident)*) => { ${count(t, 18446744073709551616)} } +} + +fn test() { + /* error: invalid macro definition: invalid metavariable expression */; + /* error: invalid macro definition: invalid metavariable expression */; + /* error: invalid macro definition: invalid metavariable expression */; +} +"#]], + ); +} + +#[test] +fn count_interaction_with_empty_binding() { + // FIXME: Should this error? rustc currently accepts it. + check( + r#" +macro_rules! m { + ($($t:ident),*) => { + ${count(t, 100)} + } +} + +fn test() { + m!(); +} +"#, + expect![[r#" +macro_rules! m { + ($($t:ident),*) => { + ${count(t, 100)} + } +} + +fn test() { + 0; +} +"#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index b663a2917897..d8e4a4dcc7c2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -297,55 +297,55 @@ extern "rust-call" fn call_once(self, ($( $arg, )*): ($( $ArgTy, )*)) -> $Return #[derive(Clone)] struct CharEscapeDebugContinue; impl Fn<(char, )> for CharEscapeDebugContinue { - #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeDebug { { + #[inline] extern "rust-call" fn call(&self , (c, ): (char, )) -> char::EscapeDebug { { c.escape_debug_ext(false ) } } } impl FnMut<(char, )> for CharEscapeDebugContinue { - #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDebug { + #[inline] extern "rust-call" fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDebug { Fn::call(&*self , (c, )) } } impl FnOnce<(char, )> for CharEscapeDebugContinue { type Output = char::EscapeDebug; - #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeDebug { + #[inline] extern "rust-call" fn call_once(self , (c, ): (char, )) -> char::EscapeDebug { Fn::call(&self , (c, )) } } #[derive(Clone)] struct CharEscapeUnicode; impl Fn<(char, )> for CharEscapeUnicode { - #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeUnicode { { + #[inline] extern "rust-call" fn call(&self , (c, ): (char, )) -> char::EscapeUnicode { { c.escape_unicode() } } } impl FnMut<(char, )> for CharEscapeUnicode { - #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeUnicode { + #[inline] extern "rust-call" fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeUnicode { Fn::call(&*self , (c, )) } } impl FnOnce<(char, )> for CharEscapeUnicode { type Output = char::EscapeUnicode; - #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeUnicode { + #[inline] extern "rust-call" fn call_once(self , (c, ): (char, )) -> char::EscapeUnicode { Fn::call(&self , (c, )) } } #[derive(Clone)] struct CharEscapeDefault; impl Fn<(char, )> for CharEscapeDefault { - #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeDefault { { + #[inline] extern "rust-call" fn call(&self , (c, ): (char, )) -> char::EscapeDefault { { c.escape_default() } } } impl FnMut<(char, )> for CharEscapeDefault { - #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDefault { + #[inline] extern "rust-call" fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDefault { Fn::call(&*self , (c, )) } } impl FnOnce<(char, )> for CharEscapeDefault { type Output = char::EscapeDefault; - #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeDefault { + #[inline] extern "rust-call" fn call_once(self , (c, ): (char, )) -> char::EscapeDefault { Fn::call(&self , (c, )) } } @@ -833,7 +833,7 @@ pub fn new() { /* parse error: expected SEMICOLON */ /* parse error: expected expression, item or let statement */ pub fn new() { - let _ = 0as u32<<(8+8); + let _ = 0 as u32<<(8+8); } // MACRO_ITEMS@0..31 // FN@0..31 diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs index b8d2ca687c9e..ae56934f632f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs @@ -98,7 +98,7 @@ macro_rules! m1 { ($x:ident) => { ($x } } macro_rules! m2 { ($x:ident) => {} } /* error: invalid macro definition: expected subtree */ -/* error: Failed to lower macro args to token tree */ +/* error: invalid token tree */ "#]], ) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs similarity index 86% rename from src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs rename to src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index 314bf22b95ee..4a62696df081 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -14,7 +14,7 @@ mod builtin_derive_macro; mod proc_macros; -use std::{iter, ops::Range, sync::Arc}; +use std::{iter, ops::Range, sync}; use ::mbe::TokenMap; use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase}; @@ -33,8 +33,13 @@ use tt::token_id::{Subtree, TokenId}; use crate::{ - db::DefDatabase, macro_id_to_def_id, nameres::ModuleSource, resolver::HasResolver, - src::HasSource, test_db::TestDB, AdtId, AsMacroCall, Lookup, ModuleDefId, + db::DefDatabase, + macro_id_to_def_id, + nameres::{DefMap, MacroSubNs, ModuleSource}, + resolver::HasResolver, + src::HasSource, + test_db::TestDB, + AdtId, AsMacroCall, Lookup, ModuleDefId, }; #[track_caller] @@ -50,13 +55,13 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream ProcMacro { name: "identity_when_valid".into(), kind: base_db::ProcMacroKind::Attr, - expander: Arc::new(IdentityWhenValidProcMacroExpander), + expander: sync::Arc::new(IdentityWhenValidProcMacroExpander), }, )]; let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros); let krate = db.crate_graph().iter().next().unwrap(); let def_map = db.crate_def_map(krate); - let local_id = def_map.root(); + let local_id = DefMap::ROOT; let module = def_map.module_id(local_id); let resolver = module.resolver(&db); let source = def_map[local_id].definition_source(&db); @@ -125,21 +130,17 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) { let macro_call = InFile::new(source.file_id, ¯o_call); - let mut error = None; - let macro_call_id = macro_call - .as_call_id_with_errors( - &db, - krate, - |path| { - resolver.resolve_path_as_macro(&db, &path).map(|it| macro_id_to_def_id(&db, it)) - }, - &mut |err| error = Some(err), - ) - .unwrap() + let res = macro_call + .as_call_id_with_errors(&db, krate, |path| { + resolver + .resolve_path_as_macro(&db, &path, Some(MacroSubNs::Bang)) + .map(|it| macro_id_to_def_id(&db, it)) + }) .unwrap(); + let macro_call_id = res.value.unwrap(); let macro_file = MacroFile { macro_call_id }; let mut expansion_result = db.parse_macro_expansion(macro_file); - expansion_result.err = expansion_result.err.or(error); + expansion_result.err = expansion_result.err.or(res.err); expansions.push((macro_call.value.clone(), expansion_result, db.macro_arg(macro_call_id))); } @@ -157,34 +158,33 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream if let Some(err) = exp.err { format_to!(expn_text, "/* error: {} */", err); } - if let Some((parse, token_map)) = exp.value { - if expect_errors { - assert!(!parse.errors().is_empty(), "no parse errors in expansion"); - for e in parse.errors() { - format_to!(expn_text, "/* parse error: {} */\n", e); - } - } else { - assert!( - parse.errors().is_empty(), - "parse errors in expansion: \n{:#?}", - parse.errors() - ); + let (parse, token_map) = exp.value; + if expect_errors { + assert!(!parse.errors().is_empty(), "no parse errors in expansion"); + for e in parse.errors() { + format_to!(expn_text, "/* parse error: {} */\n", e); } - let pp = pretty_print_macro_expansion( - parse.syntax_node(), - show_token_ids.then_some(&*token_map), + } else { + assert!( + parse.errors().is_empty(), + "parse errors in expansion: \n{:#?}", + parse.errors() ); - let indent = IndentLevel::from_node(call.syntax()); - let pp = reindent(indent, pp); - format_to!(expn_text, "{}", pp); + } + let pp = pretty_print_macro_expansion( + parse.syntax_node(), + show_token_ids.then_some(&*token_map), + ); + let indent = IndentLevel::from_node(call.syntax()); + let pp = reindent(indent, pp); + format_to!(expn_text, "{}", pp); - if tree { - let tree = format!("{:#?}", parse.syntax_node()) - .split_inclusive('\n') - .map(|line| format!("// {line}")) - .collect::(); - format_to!(expn_text, "\n{}", tree) - } + if tree { + let tree = format!("{:#?}", parse.syntax_node()) + .split_inclusive('\n') + .map(|line| format!("// {line}")) + .collect::(); + format_to!(expn_text, "\n{}", tree) } let range = call.syntax().text_range(); let range: Range = range.into(); @@ -287,6 +287,7 @@ fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> Str let curr_kind = token.kind(); let space = match (prev_kind, curr_kind) { _ if prev_kind.is_trivia() || curr_kind.is_trivia() => "", + _ if prev_kind.is_literal() && !curr_kind.is_punct() => " ", (T!['{'], T!['}']) => "", (T![=], _) | (_, T![=]) => " ", (_, T!['{']) => " ", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index 4efe8c58a69e..9b520bc3030f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -57,9 +57,9 @@ #[cfg(test)] mod tests; -use std::{cmp::Ord, ops::Deref, sync::Arc}; +use std::{cmp::Ord, ops::Deref}; -use base_db::{CrateId, Edition, FileId}; +use base_db::{CrateId, Edition, FileId, ProcMacroKind}; use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId}; use itertools::Itertools; use la_arena::Arena; @@ -67,6 +67,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use stdx::format_to; use syntax::{ast, SmolStr}; +use triomphe::Arc; use crate::{ db::DefDatabase, @@ -76,7 +77,8 @@ path::ModPath, per_ns::PerNs, visibility::Visibility, - AstId, BlockId, BlockLoc, FunctionId, LocalModuleId, MacroId, ModuleId, ProcMacroId, + AstId, BlockId, BlockLoc, FunctionId, LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, + ProcMacroId, }; /// Contains the results of (early) name resolution. @@ -92,7 +94,6 @@ pub struct DefMap { _c: Count, block: Option, - root: LocalModuleId, modules: Arena, krate: CrateId, /// The prelude module for this crate. This either comes from an import @@ -102,7 +103,22 @@ pub struct DefMap { /// but that attribute is nightly and when used in a block, it affects resolution globally /// so we aren't handling this correctly anyways). prelude: Option, - /// The extern prelude is only populated for non-block DefMaps + /// `macro_use` prelude that contains macros from `#[macro_use]`'d external crates. Note that + /// this contains all kinds of macro, not just `macro_rules!` macro. + macro_use_prelude: FxHashMap, + + /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper + /// attributes. + derive_helpers_in_scope: FxHashMap, Vec<(Name, MacroId, MacroCallId)>>, + + diagnostics: Vec, + + data: Arc, +} + +/// Data that belongs to a crate which is shared between a crate's def map and all its block def maps. +#[derive(Clone, Debug, PartialEq, Eq)] +struct DefMapCrateData { extern_prelude: FxHashMap, /// Side table for resolving derive helpers. @@ -110,9 +126,6 @@ pub struct DefMap { fn_proc_macro_mapping: FxHashMap, /// The error that occurred when failing to load the proc-macro dll. proc_macro_loading_error: Option>, - /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper - /// attributes. - derive_helpers_in_scope: FxHashMap, Vec<(Name, MacroId, MacroCallId)>>, /// Custom attributes registered with `#![register_attr]`. registered_attrs: Vec, @@ -122,10 +135,36 @@ pub struct DefMap { unstable_features: FxHashSet, /// #[rustc_coherence_is_core] rustc_coherence_is_core: bool, + no_core: bool, + no_std: bool, edition: Edition, recursion_limit: Option, - diagnostics: Vec, +} + +impl DefMapCrateData { + fn shrink_to_fit(&mut self) { + let Self { + extern_prelude, + exported_derives, + fn_proc_macro_mapping, + registered_attrs, + registered_tools, + unstable_features, + proc_macro_loading_error: _, + rustc_coherence_is_core: _, + no_core: _, + no_std: _, + edition: _, + recursion_limit: _, + } = self; + extern_prelude.shrink_to_fit(); + exported_derives.shrink_to_fit(); + fn_proc_macro_mapping.shrink_to_fit(); + registered_attrs.shrink_to_fit(); + registered_tools.shrink_to_fit(); + unstable_features.shrink_to_fit(); + } } /// For `DefMap`s computed for a block expression, this stores its location in the parent map. @@ -134,7 +173,23 @@ struct BlockInfo { /// The `BlockId` this `DefMap` was created from. block: BlockId, /// The containing module. - parent: ModuleId, + parent: BlockRelativeModuleId, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +struct BlockRelativeModuleId { + block: Option, + local_id: LocalModuleId, +} + +impl BlockRelativeModuleId { + fn def_map(self, db: &dyn DefDatabase, krate: CrateId) -> Arc { + self.into_module(krate).def_map(db) + } + + fn into_module(self, krate: CrateId) -> ModuleId { + ModuleId { krate, block: self.block, local_id: self.local_id } + } } impl std::ops::Index for DefMap { @@ -224,6 +279,8 @@ pub struct ModuleData { } impl DefMap { + pub const ROOT: LocalModuleId = LocalModuleId::from_raw(la_arena::RawIdx::from_u32(0)); + pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc { let _p = profile::span("crate_def_map_query").detail(|| { db.crate_graph()[krate].display_name.as_deref().unwrap_or_default().to_string() @@ -243,17 +300,10 @@ pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc Option> { + pub(crate) fn block_def_map_query(db: &dyn DefDatabase, block_id: BlockId) -> Arc { let block: BlockLoc = db.lookup_intern_block(block_id); let tree_id = TreeId::new(block.ast_id.file_id, Some(block_id)); - let item_tree = tree_id.item_tree(db); - if item_tree.top_level_items().is_empty() { - return None; - } let parent_map = block.module.def_map(db); let krate = block.module.krate; @@ -265,36 +315,48 @@ pub(crate) fn block_def_map_query( let module_data = ModuleData::new(ModuleOrigin::BlockExpr { block: block.ast_id }, visibility); - let mut def_map = DefMap::empty(krate, parent_map.edition, module_data); - def_map.block = Some(BlockInfo { block: block_id, parent: block.module }); + let mut def_map = DefMap::empty(krate, parent_map.data.edition, module_data); + def_map.data = parent_map.data.clone(); + def_map.block = Some(BlockInfo { + block: block_id, + parent: BlockRelativeModuleId { + block: block.module.block, + local_id: block.module.local_id, + }, + }); let def_map = collector::collect_defs(db, def_map, tree_id); - Some(Arc::new(def_map)) + Arc::new(def_map) } fn empty(krate: CrateId, edition: Edition, module_data: ModuleData) -> DefMap { let mut modules: Arena = Arena::default(); let root = modules.alloc(module_data); + assert_eq!(root, Self::ROOT); DefMap { _c: Count::new(), block: None, - krate, - edition, - recursion_limit: None, - extern_prelude: FxHashMap::default(), - exported_derives: FxHashMap::default(), - fn_proc_macro_mapping: FxHashMap::default(), - proc_macro_loading_error: None, - derive_helpers_in_scope: FxHashMap::default(), - prelude: None, - root, modules, - registered_attrs: Vec::new(), - registered_tools: Vec::new(), - unstable_features: FxHashSet::default(), + krate, + prelude: None, + macro_use_prelude: FxHashMap::default(), + derive_helpers_in_scope: FxHashMap::default(), diagnostics: Vec::new(), - rustc_coherence_is_core: false, + data: Arc::new(DefMapCrateData { + extern_prelude: FxHashMap::default(), + exported_derives: FxHashMap::default(), + fn_proc_macro_mapping: FxHashMap::default(), + proc_macro_loading_error: None, + registered_attrs: Vec::new(), + registered_tools: Vec::new(), + unstable_features: FxHashSet::default(), + rustc_coherence_is_core: false, + no_core: false, + no_std: false, + edition, + recursion_limit: None, + }), } } @@ -317,31 +379,31 @@ pub fn derive_helpers_in_scope( } pub fn registered_tools(&self) -> &[SmolStr] { - &self.registered_tools + &self.data.registered_tools } pub fn registered_attrs(&self) -> &[SmolStr] { - &self.registered_attrs + &self.data.registered_attrs } pub fn is_unstable_feature_enabled(&self, feature: &str) -> bool { - self.unstable_features.contains(feature) + self.data.unstable_features.contains(feature) } pub fn is_rustc_coherence_is_core(&self) -> bool { - self.rustc_coherence_is_core + self.data.rustc_coherence_is_core } - pub fn root(&self) -> LocalModuleId { - self.root + pub fn is_no_std(&self) -> bool { + self.data.no_std || self.data.no_core } pub fn fn_as_proc_macro(&self, id: FunctionId) -> Option { - self.fn_proc_macro_mapping.get(&id).copied() + self.data.fn_proc_macro_mapping.get(&id).copied() } pub fn proc_macro_loading_error(&self) -> Option<&str> { - self.proc_macro_loading_error.as_deref() + self.data.proc_macro_loading_error.as_deref() } pub fn krate(&self) -> CrateId { @@ -356,8 +418,12 @@ pub(crate) fn prelude(&self) -> Option { self.prelude } - pub(crate) fn extern_prelude(&self) -> impl Iterator + '_ { - self.extern_prelude.iter() + pub(crate) fn extern_prelude(&self) -> impl Iterator + '_ { + self.data.extern_prelude.iter().map(|(name, def)| (name, *def)) + } + + pub(crate) fn macro_use_prelude(&self) -> impl Iterator + '_ { + self.macro_use_prelude.iter().map(|(name, def)| (name, *def)) } pub fn module_id(&self, local_id: LocalModuleId) -> ModuleId { @@ -365,11 +431,8 @@ pub fn module_id(&self, local_id: LocalModuleId) -> ModuleId { ModuleId { krate: self.krate, local_id, block } } - pub(crate) fn crate_root(&self, db: &dyn DefDatabase) -> ModuleId { - self.with_ancestor_maps(db, self.root, &mut |def_map, _module| { - if def_map.block.is_none() { Some(def_map.module_id(def_map.root)) } else { None } - }) - .expect("DefMap chain without root") + pub(crate) fn crate_root(&self) -> ModuleId { + ModuleId { krate: self.krate, block: None, local_id: DefMap::ROOT } } pub(crate) fn resolve_path( @@ -378,9 +441,16 @@ pub(crate) fn resolve_path( original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, + expected_macro_subns: Option, ) -> (PerNs, Option) { - let res = - self.resolve_path_fp_with_macro(db, ResolveMode::Other, original_module, path, shadow); + let res = self.resolve_path_fp_with_macro( + db, + ResolveMode::Other, + original_module, + path, + shadow, + expected_macro_subns, + ); (res.resolved_def, res.segment_index) } @@ -397,6 +467,7 @@ pub(crate) fn resolve_path_locally( original_module, path, shadow, + None, // Currently this function isn't used for macro resolution. ); (res.resolved_def, res.segment_index) } @@ -416,7 +487,7 @@ pub fn with_ancestor_maps( } let mut block = self.block; while let Some(block_info) = block { - let parent = block_info.parent.def_map(db); + let parent = block_info.parent.def_map(db, self.krate); if let Some(it) = f(&parent, block_info.parent.local_id) { return Some(it); } @@ -429,7 +500,8 @@ pub fn with_ancestor_maps( /// If this `DefMap` is for a block expression, returns the module containing the block (which /// might again be a block, or a module inside a block). pub fn parent(&self) -> Option { - Some(self.block?.parent) + let BlockRelativeModuleId { block, local_id } = self.block?.parent; + Some(ModuleId { krate: self.krate, block, local_id }) } /// Returns the module containing `local_mod`, either the parent `mod`, or the module (or block) containing @@ -437,7 +509,13 @@ pub fn parent(&self) -> Option { pub fn containing_module(&self, local_mod: LocalModuleId) -> Option { match self[local_mod].parent { Some(parent) => Some(self.module_id(parent)), - None => self.block.map(|block| block.parent), + None => { + self.block.map( + |BlockInfo { parent: BlockRelativeModuleId { block, local_id }, .. }| { + ModuleId { krate: self.krate, block, local_id } + }, + ) + } } } @@ -448,25 +526,31 @@ pub fn dump(&self, db: &dyn DefDatabase) -> String { let mut arc; let mut current_map = self; while let Some(block) = current_map.block { - go(&mut buf, current_map, "block scope", current_map.root); + go(&mut buf, db, current_map, "block scope", Self::ROOT); buf.push('\n'); - arc = block.parent.def_map(db); + arc = block.parent.def_map(db, self.krate); current_map = &arc; } - go(&mut buf, current_map, "crate", current_map.root); + go(&mut buf, db, current_map, "crate", Self::ROOT); return buf; - fn go(buf: &mut String, map: &DefMap, path: &str, module: LocalModuleId) { + fn go( + buf: &mut String, + db: &dyn DefDatabase, + map: &DefMap, + path: &str, + module: LocalModuleId, + ) { format_to!(buf, "{}\n", path); - map.modules[module].scope.dump(buf); + map.modules[module].scope.dump(db.upcast(), buf); for (name, child) in map.modules[module].children.iter().sorted_by(|a, b| Ord::cmp(&a.0, &b.0)) { - let path = format!("{path}::{name}"); + let path = format!("{path}::{}", name.display(db.upcast())); buf.push('\n'); - go(buf, map, &path, *child); + go(buf, db, map, &path, *child); } } } @@ -477,7 +561,7 @@ pub fn dump_block_scopes(&self, db: &dyn DefDatabase) -> String { let mut current_map = self; while let Some(block) = current_map.block { format_to!(buf, "{:?} in {:?}\n", block.block, block.parent); - arc = block.parent.def_map(db); + arc = block.parent.def_map(db, self.krate); current_map = &arc; } @@ -489,34 +573,20 @@ fn shrink_to_fit(&mut self) { // Exhaustive match to require handling new fields. let Self { _c: _, - exported_derives, - extern_prelude, + macro_use_prelude, diagnostics, modules, - registered_attrs, - registered_tools, - fn_proc_macro_mapping, derive_helpers_in_scope, - unstable_features, - proc_macro_loading_error: _, block: _, - edition: _, - recursion_limit: _, krate: _, prelude: _, - root: _, - rustc_coherence_is_core: _, + data: _, } = self; - extern_prelude.shrink_to_fit(); - exported_derives.shrink_to_fit(); + macro_use_prelude.shrink_to_fit(); diagnostics.shrink_to_fit(); modules.shrink_to_fit(); - registered_attrs.shrink_to_fit(); - registered_tools.shrink_to_fit(); - fn_proc_macro_mapping.shrink_to_fit(); derive_helpers_in_scope.shrink_to_fit(); - unstable_features.shrink_to_fit(); for (_, module) in modules.iter_mut() { module.children.shrink_to_fit(); module.scope.shrink_to_fit(); @@ -529,7 +599,7 @@ pub fn diagnostics(&self) -> &[DefDiagnostic] { } pub fn recursion_limit(&self) -> Option { - self.recursion_limit + self.data.recursion_limit } } @@ -564,3 +634,48 @@ pub enum ModuleSource { Module(ast::Module), BlockExpr(ast::BlockExpr), } + +/// See `sub_namespace_match()`. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum MacroSubNs { + /// Function-like macros, suffixed with `!`. + Bang, + /// Macros inside attributes, i.e. attribute macros and derive macros. + Attr, +} + +impl MacroSubNs { + fn from_id(db: &dyn DefDatabase, macro_id: MacroId) -> Self { + let expander = match macro_id { + MacroId::Macro2Id(it) => it.lookup(db).expander, + MacroId::MacroRulesId(it) => it.lookup(db).expander, + MacroId::ProcMacroId(it) => { + return match it.lookup(db).kind { + ProcMacroKind::CustomDerive | ProcMacroKind::Attr => Self::Attr, + ProcMacroKind::FuncLike => Self::Bang, + }; + } + }; + + // Eager macros aren't *guaranteed* to be bang macros, but they *are* all bang macros currently. + match expander { + MacroExpander::Declarative + | MacroExpander::BuiltIn(_) + | MacroExpander::BuiltInEager(_) => Self::Bang, + MacroExpander::BuiltInAttr(_) | MacroExpander::BuiltInDerive(_) => Self::Attr, + } + } +} + +/// Quoted from [rustc]: +/// Macro namespace is separated into two sub-namespaces, one for bang macros and +/// one for attribute-like macros (attributes, derives). +/// We ignore resolutions from one sub-namespace when searching names in scope for another. +/// +/// [rustc]: https://github.com/rust-lang/rust/blob/1.69.0/compiler/rustc_resolve/src/macros.rs#L75 +fn sub_namespace_match(candidate: Option, expected: Option) -> bool { + match (candidate, expected) { + (Some(candidate), Some(expected)) => candidate == expected, + _ => true, + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs index 79cabeb0fb7a..a7abf445918a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs @@ -4,7 +4,8 @@ use syntax::{ast, SmolStr}; use crate::{ - attr_macro_as_call_id, builtin_attr, + attr::builtin::{find_builtin_attr_idx, TOOL_MODULES}, + attr_macro_as_call_id, db::DefDatabase, item_scope::BuiltinShadowMode, macro_id_to_def_id, @@ -13,7 +14,7 @@ AstIdWithPath, LocalModuleId, UnresolvedMacro, }; -use super::DefMap; +use super::{DefMap, MacroSubNs}; pub enum ResolvedAttr { /// Attribute resolved to an attribute macro. @@ -42,9 +43,12 @@ pub(crate) fn resolve_attr_macro( original_module, &ast_id.path, BuiltinShadowMode::Module, + Some(MacroSubNs::Attr), ); let def = match resolved_res.resolved_def.take_macros() { Some(def) => { + // `MacroSubNs` is just a hint, so the path may still resolve to a custom derive + // macro, or even function-like macro when the path is qualified. if def.is_attribute(db) { def } else { @@ -60,7 +64,6 @@ pub(crate) fn resolve_attr_macro( attr, self.krate, macro_id_to_def_id(db, def), - false, ))) } @@ -75,20 +78,16 @@ pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool { let name = name.to_smol_str(); let pred = |n: &_| *n == name; - let registered = self.registered_tools.iter().map(SmolStr::as_str); - let is_tool = builtin_attr::TOOL_MODULES.iter().copied().chain(registered).any(pred); + let registered = self.data.registered_tools.iter().map(SmolStr::as_str); + let is_tool = TOOL_MODULES.iter().copied().chain(registered).any(pred); // FIXME: tool modules can be shadowed by actual modules if is_tool { return true; } if segments.len() == 1 { - let registered = self.registered_attrs.iter().map(SmolStr::as_str); - let is_inert = builtin_attr::INERT_ATTRIBUTES - .iter() - .map(|it| it.name) - .chain(registered) - .any(pred); + let mut registered = self.data.registered_attrs.iter().map(SmolStr::as_str); + let is_inert = find_builtin_attr_idx(&name).is_some() || registered.any(pred); return is_inert; } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index ddcee77ec4cc..06542b4b1e99 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -5,7 +5,7 @@ use std::{iter, mem}; -use base_db::{CrateId, Edition, FileId}; +use base_db::{CrateId, Dependency, Edition, FileId}; use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ @@ -14,10 +14,11 @@ builtin_attr_macro::find_builtin_attr, builtin_derive_macro::find_builtin_derive, builtin_fn_macro::find_builtin_macro, + hygiene::Hygiene, name::{name, AsName, Name}, proc_macro::ProcMacroExpander, - ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, - MacroDefKind, + ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc, + MacroDefId, MacroDefKind, }; use itertools::{izip, Itertools}; use la_arena::Idx; @@ -25,6 +26,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use stdx::always; use syntax::{ast, SmolStr}; +use triomphe::Arc; use crate::{ attr::Attrs, @@ -33,8 +35,8 @@ derive_macro_as_call_id, item_scope::{ImportType, PerNsGlobImports}, item_tree::{ - self, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall, - MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, + self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, + MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, }, macro_call_as_call_id, macro_id_to_def_id, nameres::{ @@ -42,7 +44,8 @@ mod_resolution::ModDir, path_resolution::ReachedFixedPoint, proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroDef, ProcMacroKind}, - BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode, + sub_namespace_match, BuiltinShadowMode, DefMap, MacroSubNs, ModuleData, ModuleOrigin, + ResolveMode, }, path::{ImportAlias, ModPath, PathKind}, per_ns::PerNs, @@ -59,7 +62,7 @@ static EXPANSION_DEPTH_LIMIT: Limit = Limit::new(128); static FIXED_POINT_LIMIT: Limit = Limit::new(8192); -pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: TreeId) -> DefMap { +pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeId) -> DefMap { let crate_graph = db.crate_graph(); let mut deps = FxHashMap::default(); @@ -67,36 +70,33 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T let krate = &crate_graph[def_map.krate]; for dep in &krate.dependencies { tracing::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id); - let dep_def_map = db.crate_def_map(dep.crate_id); - let dep_root = dep_def_map.module_id(dep_def_map.root); - deps.insert(dep.as_name(), dep_root); - - if dep.is_prelude() && !tree_id.is_block() { - def_map.extern_prelude.insert(dep.as_name(), dep_root); - } + deps.insert(dep.as_name(), dep.clone()); } let cfg_options = &krate.cfg_options; - let proc_macros = match &krate.proc_macro { - Ok(proc_macros) => { - proc_macros - .iter() - .enumerate() - .map(|(idx, it)| { - // FIXME: a hacky way to create a Name from string. - let name = - tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() }; - (name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32))) - }) - .collect() - } - Err(e) => { - def_map.proc_macro_loading_error = Some(e.clone().into_boxed_str()); - Vec::new() - } - }; + let is_proc_macro = krate.is_proc_macro; + let proc_macros = if is_proc_macro { + match db.proc_macros().get(&def_map.krate) { + Some(Ok(proc_macros)) => { + Ok(proc_macros + .iter() + .enumerate() + .map(|(idx, it)| { + // FIXME: a hacky way to create a Name from string. + let name = + tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() }; + (name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32))) + }) + .collect()) + } + Some(Err(e)) => Err(e.clone().into_boxed_str()), + None => Err("No proc-macros present for crate".to_owned().into_boxed_str()), + } + } else { + Ok(vec![]) + }; let mut collector = DefCollector { db, @@ -112,6 +112,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T from_glob_import: Default::default(), skip_attrs: Default::default(), is_proc_macro, + hygienes: FxHashMap::default(), }; if tree_id.is_block() { collector.seed_with_inner(tree_id); @@ -238,7 +239,7 @@ enum MacroDirectiveKind { struct DefCollector<'a> { db: &'a dyn DefDatabase, def_map: DefMap, - deps: FxHashMap, + deps: FxHashMap, glob_imports: FxHashMap>, unresolved_imports: Vec, indeterminate_imports: Vec, @@ -249,7 +250,7 @@ struct DefCollector<'a> { /// built by the build system, and is the list of proc. macros we can actually expand. It is /// empty when proc. macro support is disabled (in which case we still do name resolution for /// them). - proc_macros: Vec<(Name, ProcMacroExpander)>, + proc_macros: Result, Box>, is_proc_macro: bool, from_glob_import: PerNsGlobImports, /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute. @@ -259,6 +260,12 @@ struct DefCollector<'a> { /// This also stores the attributes to skip when we resolve derive helpers and non-macro /// non-builtin attributes in general. skip_attrs: FxHashMap, AttrId>, + /// `Hygiene` cache, because `Hygiene` construction is expensive. + /// + /// Almost all paths should have been lowered to `ModPath` during `ItemTree` construction. + /// However, `DefCollector` still needs to lower paths in attributes, in particular those in + /// derive meta item list. + hygienes: FxHashMap, } impl DefCollector<'_> { @@ -267,86 +274,117 @@ fn seed_with_top_level(&mut self) { let file_id = self.db.crate_graph()[self.def_map.krate].root_file_id; let item_tree = self.db.file_item_tree(file_id.into()); - let module_id = self.def_map.root; + let module_id = DefMap::ROOT; let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate); - if attrs.cfg().map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)) { - self.inject_prelude(&attrs); + let crate_data = Arc::get_mut(&mut self.def_map.data).unwrap(); - // Process other crate-level attributes. - for attr in &*attrs { - let attr_name = match attr.path.as_ident() { - Some(name) => name, - None => continue, - }; - - if *attr_name == hir_expand::name![recursion_limit] { - if let Some(limit) = attr.string_value() { - if let Ok(limit) = limit.parse() { - self.def_map.recursion_limit = Some(limit); - } - } - continue; - } - - if *attr_name == hir_expand::name![crate_type] { - if let Some("proc-macro") = attr.string_value().map(SmolStr::as_str) { - self.is_proc_macro = true; - } - continue; - } - - if attr_name.as_text().as_deref() == Some("rustc_coherence_is_core") { - self.def_map.rustc_coherence_is_core = true; - continue; - } - - if *attr_name == hir_expand::name![feature] { - let features = - attr.parse_path_comma_token_tree().into_iter().flatten().filter_map( - |feat| match feat.segments() { - [name] => Some(name.to_smol_str()), - _ => None, - }, - ); - self.def_map.unstable_features.extend(features); - } - - let attr_is_register_like = *attr_name == hir_expand::name![register_attr] - || *attr_name == hir_expand::name![register_tool]; - if !attr_is_register_like { - continue; - } - - let registered_name = match attr.single_ident_value() { - Some(ident) => ident.as_name(), - _ => continue, - }; - - if *attr_name == hir_expand::name![register_attr] { - self.def_map.registered_attrs.push(registered_name.to_smol_str()); - cov_mark::hit!(register_attr); - } else { - self.def_map.registered_tools.push(registered_name.to_smol_str()); - cov_mark::hit!(register_tool); - } - } - - ModCollector { - def_collector: self, - macro_depth: 0, - module_id, - tree_id: TreeId::new(file_id.into(), None), - item_tree: &item_tree, - mod_dir: ModDir::root(), - } - .collect_in_top_module(item_tree.top_level_items()); + if let Err(e) = &self.proc_macros { + crate_data.proc_macro_loading_error = Some(e.clone()); } + + for (name, dep) in &self.deps { + if dep.is_prelude() { + crate_data.extern_prelude.insert( + name.clone(), + ModuleId { krate: dep.crate_id, block: None, local_id: DefMap::ROOT }, + ); + } + } + + // Process other crate-level attributes. + for attr in &*attrs { + if let Some(cfg) = attr.cfg() { + if self.cfg_options.check(&cfg) == Some(false) { + return; + } + } + let attr_name = match attr.path.as_ident() { + Some(name) => name, + None => continue, + }; + + if *attr_name == hir_expand::name![recursion_limit] { + if let Some(limit) = attr.string_value() { + if let Ok(limit) = limit.parse() { + crate_data.recursion_limit = Some(limit); + } + } + continue; + } + + if *attr_name == hir_expand::name![crate_type] { + if let Some("proc-macro") = attr.string_value().map(SmolStr::as_str) { + self.is_proc_macro = true; + } + continue; + } + + if *attr_name == hir_expand::name![no_core] { + crate_data.no_core = true; + continue; + } + + if *attr_name == hir_expand::name![no_std] { + crate_data.no_std = true; + continue; + } + + if attr_name.as_text().as_deref() == Some("rustc_coherence_is_core") { + crate_data.rustc_coherence_is_core = true; + continue; + } + + if *attr_name == hir_expand::name![feature] { + let hygiene = &Hygiene::new_unhygienic(); + let features = attr + .parse_path_comma_token_tree(self.db.upcast(), hygiene) + .into_iter() + .flatten() + .filter_map(|feat| match feat.segments() { + [name] => Some(name.to_smol_str()), + _ => None, + }); + crate_data.unstable_features.extend(features); + } + + let attr_is_register_like = *attr_name == hir_expand::name![register_attr] + || *attr_name == hir_expand::name![register_tool]; + if !attr_is_register_like { + continue; + } + + let registered_name = match attr.single_ident_value() { + Some(ident) => ident.as_name(), + _ => continue, + }; + + if *attr_name == hir_expand::name![register_attr] { + crate_data.registered_attrs.push(registered_name.to_smol_str()); + cov_mark::hit!(register_attr); + } else { + crate_data.registered_tools.push(registered_name.to_smol_str()); + cov_mark::hit!(register_tool); + } + } + + crate_data.shrink_to_fit(); + self.inject_prelude(); + + ModCollector { + def_collector: self, + macro_depth: 0, + module_id, + tree_id: TreeId::new(file_id.into(), None), + item_tree: &item_tree, + mod_dir: ModDir::root(), + } + .collect_in_top_module(item_tree.top_level_items()); } fn seed_with_inner(&mut self, tree_id: TreeId) { let item_tree = tree_id.item_tree(self.db); - let module_id = self.def_map.root; + let module_id = DefMap::ROOT; let is_cfg_enabled = item_tree .top_level_attrs(self.db, self.def_map.krate) @@ -428,7 +466,7 @@ fn collect(&mut self) { // Additionally, while the proc macro entry points must be `pub`, they are not publicly // exported in type/value namespace. This function reduces the visibility of all items // in the crate root that aren't proc macros. - let root = self.def_map.root; + let root = DefMap::ROOT; let module_id = self.def_map.module_id(root); let root = &mut self.def_map.modules[root]; root.scope.censor_non_proc_macros(module_id); @@ -456,12 +494,8 @@ fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint { directive.module_id, MacroCallKind::Attr { ast_id: ast_id.ast_id, - attr_args: std::sync::Arc::new(( - tt::Subtree::empty(), - Default::default(), - )), + attr_args: Arc::new((tt::Subtree::empty(), Default::default())), invoc_attr_index: attr.id, - is_derive: false, }, attr.path().clone(), )); @@ -495,15 +529,15 @@ fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint { } } - fn inject_prelude(&mut self, crate_attrs: &Attrs) { + fn inject_prelude(&mut self) { // See compiler/rustc_builtin_macros/src/standard_library_imports.rs - if crate_attrs.by_key("no_core").exists() { + if self.def_map.data.no_core { // libcore does not get a prelude. return; } - let krate = if crate_attrs.by_key("no_std").exists() { + let krate = if self.def_map.data.no_std { name![core] } else { let std = name![std]; @@ -516,43 +550,31 @@ fn inject_prelude(&mut self, crate_attrs: &Attrs) { } }; - let edition = match self.def_map.edition { + let edition = match self.def_map.data.edition { Edition::Edition2015 => name![rust_2015], Edition::Edition2018 => name![rust_2018], Edition::Edition2021 => name![rust_2021], }; - let path_kind = match self.def_map.edition { + let path_kind = match self.def_map.data.edition { Edition::Edition2015 => PathKind::Plain, _ => PathKind::Abs, }; - let path = - ModPath::from_segments(path_kind, [krate.clone(), name![prelude], edition].into_iter()); - // Fall back to the older `std::prelude::v1` for compatibility with Rust <1.52.0 - // FIXME remove this fallback - let fallback_path = - ModPath::from_segments(path_kind, [krate, name![prelude], name![v1]].into_iter()); + let path = ModPath::from_segments(path_kind, [krate, name![prelude], edition]); - for path in &[path, fallback_path] { - let (per_ns, _) = self.def_map.resolve_path( - self.db, - self.def_map.root, - path, - BuiltinShadowMode::Other, - ); + let (per_ns, _) = + self.def_map.resolve_path(self.db, DefMap::ROOT, &path, BuiltinShadowMode::Other, None); - match per_ns.types { - Some((ModuleDefId::ModuleId(m), _)) => { - self.def_map.prelude = Some(m); - break; - } - types => { - tracing::debug!( - "could not resolve prelude path `{}` to module (resolved to {:?})", - path, - types - ); - } + match per_ns.types { + Some((ModuleDefId::ModuleId(m), _)) => { + self.def_map.prelude = Some(m); + } + types => { + tracing::debug!( + "could not resolve prelude path `{}` to module (resolved to {:?})", + path.display(self.db.upcast()), + types + ); } } } @@ -578,23 +600,29 @@ fn export_proc_macro( def: ProcMacroDef, id: ItemTreeId, fn_id: FunctionId, - module_id: ModuleId, ) { + if self.def_map.block.is_some() { + return; + } + let crate_root = self.def_map.module_id(DefMap::ROOT); + let kind = def.kind.to_basedb_kind(); - let (expander, kind) = match self.proc_macros.iter().find(|(n, _)| n == &def.name) { - Some(&(_, expander)) => (expander, kind), - None => (ProcMacroExpander::dummy(), kind), - }; + let (expander, kind) = + match self.proc_macros.as_ref().map(|it| it.iter().find(|(n, _)| n == &def.name)) { + Ok(Some(&(_, expander))) => (expander, kind), + _ => (ProcMacroExpander::dummy(), kind), + }; let proc_macro_id = - ProcMacroLoc { container: module_id, id, expander, kind }.intern(self.db); + ProcMacroLoc { container: crate_root, id, expander, kind }.intern(self.db); self.define_proc_macro(def.name.clone(), proc_macro_id); + let crate_data = Arc::get_mut(&mut self.def_map.data).unwrap(); if let ProcMacroKind::CustomDerive { helpers } = def.kind { - self.def_map + crate_data .exported_derives .insert(macro_id_to_def_id(self.db, proc_macro_id.into()), helpers); } - self.def_map.fn_proc_macro_mapping.insert(fn_id, proc_macro_id); + crate_data.fn_proc_macro_mapping.insert(fn_id, proc_macro_id); } /// Define a macro with `macro_rules`. @@ -636,7 +664,7 @@ fn define_macro_rules( // In Rust, `#[macro_export]` macros are unconditionally visible at the // crate root, even if the parent modules is **not** visible. if export { - let module_id = self.def_map.root; + let module_id = DefMap::ROOT; self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, @@ -687,7 +715,7 @@ fn define_macro_def( /// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped. /// And unconditionally exported. fn define_proc_macro(&mut self, name: Name, macro_: ProcMacroId) { - let module_id = self.def_map.root; + let module_id = DefMap::ROOT; self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, @@ -697,39 +725,28 @@ fn define_proc_macro(&mut self, name: Name, macro_: ProcMacroId) { ); } - /// Import macros from `#[macro_use] extern crate`. - fn import_macros_from_extern_crate( - &mut self, - current_module_id: LocalModuleId, - extern_crate: &item_tree::ExternCrate, - ) { - tracing::debug!( - "importing macros from extern crate: {:?} ({:?})", - extern_crate, - self.def_map.edition, - ); - - if let Some(m) = self.resolve_extern_crate(&extern_crate.name) { - if m == self.def_map.module_id(current_module_id) { - cov_mark::hit!(ignore_macro_use_extern_crate_self); - return; - } - - cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); - self.import_all_macros_exported(current_module_id, m.krate); - } - } - - /// Import all exported macros from another crate + /// Import exported macros from another crate. `names`, if `Some(_)`, specifies the name of + /// macros to be imported. Otherwise this method imports all exported macros. /// /// Exported macros are just all macros in the root module scope. /// Note that it contains not only all `#[macro_export]` macros, but also all aliases /// created by `use` in the root module, ignoring the visibility of `use`. - fn import_all_macros_exported(&mut self, current_module_id: LocalModuleId, krate: CrateId) { + fn import_macros_from_extern_crate(&mut self, krate: CrateId, names: Option>) { let def_map = self.db.crate_def_map(krate); - for (name, def) in def_map[def_map.root].scope.macros() { - // `#[macro_use]` brings macros into legacy scope. Yes, even non-`macro_rules!` macros. - self.define_legacy_macro(current_module_id, name.clone(), def); + // `#[macro_use]` brings macros into macro_use prelude. Yes, even non-`macro_rules!` + // macros. + let root_scope = &def_map[DefMap::ROOT].scope; + if let Some(names) = names { + for name in names { + // FIXME: Report diagnostic on 404. + if let Some(def) = root_scope.get(&name).take_macros() { + self.def_map.macro_use_prelude.insert(name, def); + } + } + } else { + for (name, def) in root_scope.macros() { + self.def_map.macro_use_prelude.insert(name.clone(), def); + } } } @@ -762,8 +779,9 @@ fn resolve_imports(&mut self) -> ReachedFixedPoint { } fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport { - let _p = profile::span("resolve_import").detail(|| format!("{}", import.path)); - tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.edition); + let _p = profile::span("resolve_import") + .detail(|| format!("{}", import.path.display(self.db.upcast()))); + tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition); if import.is_extern_crate { let name = import .path @@ -785,6 +803,7 @@ fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialRe module_id, &import.path, BuiltinShadowMode::Module, + None, // An import may resolve to any kind of macro. ); let def = res.resolved_def; @@ -815,16 +834,13 @@ fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialRe fn resolve_extern_crate(&self, name: &Name) -> Option { if *name == name!(self) { cov_mark::hit!(extern_crate_self_as); - let root = match self.def_map.block { - Some(_) => { - let def_map = self.def_map.crate_root(self.db).def_map(self.db); - def_map.module_id(def_map.root()) - } - None => self.def_map.module_id(self.def_map.root()), - }; - Some(root) + Some(self.def_map.crate_root()) } else { - self.deps.get(name).copied() + self.deps.get(name).map(|dep| ModuleId { + krate: dep.crate_id, + block: None, + local_id: DefMap::ROOT, + }) } } @@ -863,11 +879,14 @@ fn record_resolved_import(&mut self, directive: &ImportDirective) { // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 if import.is_extern_crate && self.def_map.block.is_none() - && module_id == self.def_map.root + && module_id == DefMap::ROOT { if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = (def.take_types(), name) { - self.def_map.extern_prelude.insert(name.clone(), def); + Arc::get_mut(&mut self.def_map.data) + .unwrap() + .extern_prelude + .insert(name.clone(), def); } } @@ -1082,7 +1101,14 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { resolved.push((directive.module_id, directive.depth, directive.container, call_id)); }; let mut res = ReachedFixedPoint::Yes; + // Retain unresolved macros after this round of resolution. macros.retain(|directive| { + let subns = match &directive.kind { + MacroDirectiveKind::FnLike { .. } => MacroSubNs::Bang, + MacroDirectiveKind::Attr { .. } | MacroDirectiveKind::Derive { .. } => { + MacroSubNs::Attr + } + }; let resolver = |path| { let resolved_res = self.def_map.resolve_path_fp_with_macro( self.db, @@ -1090,6 +1116,7 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { directive.module_id, &path, BuiltinShadowMode::Module, + Some(subns), ); resolved_res .resolved_def @@ -1101,15 +1128,15 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { match &directive.kind { MacroDirectiveKind::FnLike { ast_id, expand_to } => { let call_id = macro_call_as_call_id( - self.db, + self.db.upcast(), ast_id, *expand_to, self.def_map.krate, resolver_def_id, - &mut |_err| (), ); - if let Ok(Ok(call_id)) = call_id { + if let Ok(Some(call_id)) = call_id { push_resolved(directive, call_id); + res = ReachedFixedPoint::No; return false; } @@ -1134,7 +1161,7 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { // Record its helper attributes. if def_id.krate != self.def_map.krate { let def_map = self.db.crate_def_map(def_id.krate); - if let Some(helpers) = def_map.exported_derives.get(&def_id) { + if let Some(helpers) = def_map.data.exported_derives.get(&def_id) { self.def_map .derive_helpers_in_scope .entry(ast_id.ast_id.map(|it| it.upcast())) @@ -1214,7 +1241,19 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { }; let ast_id = ast_id.with_value(ast_adt_id); - match attr.parse_path_comma_token_tree() { + let extend_unhygenic; + let hygiene = if file_id.is_macro() { + self.hygienes + .entry(file_id) + .or_insert_with(|| Hygiene::new(self.db.upcast(), file_id)) + } else { + // Avoid heap allocation (`Hygiene` embraces `Arc`) and hash map entry + // when we're in an oridinary (non-macro) file. + extend_unhygenic = Hygiene::new_unhygienic(); + &extend_unhygenic + }; + + match attr.parse_path_comma_token_tree(self.db.upcast(), hygiene) { Some(derive_macros) => { let mut len = 0; for (idx, path) in derive_macros.enumerate() { @@ -1241,7 +1280,6 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { attr, self.def_map.krate, def, - true, ); self.def_map.modules[directive.module_id] .scope @@ -1261,18 +1299,12 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { } // Not resolved to a derive helper or the derive attribute, so try to treat as a normal attribute. - let call_id = attr_macro_as_call_id( - self.db, - file_ast_id, - attr, - self.def_map.krate, - def, - false, - ); + let call_id = + attr_macro_as_call_id(self.db, file_ast_id, attr, self.def_map.krate, def); let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id); // If proc attribute macro expansion is disabled, skip expanding it here - if !self.db.enable_proc_attr_macros() { + if !self.db.expand_proc_attr_macros() { self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro( directive.module_id, loc.kind, @@ -1345,25 +1377,31 @@ fn collect_macro_expansion( let file_id = macro_call_id.as_file(); // First, fetch the raw expansion result for purposes of error reporting. This goes through - // `macro_expand_error` to avoid depending on the full expansion result (to improve + // `parse_macro_expansion_error` to avoid depending on the full expansion result (to improve // incrementality). - let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id); - let err = self.db.macro_expand_error(macro_call_id); + let ExpandResult { value, err } = self.db.parse_macro_expansion_error(macro_call_id); if let Some(err) = err { + let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id); let diag = match err { + // why is this reported here? hir_expand::ExpandError::UnresolvedProcMacro(krate) => { always!(krate == loc.def.krate); - // Missing proc macros are non-fatal, so they are handled specially. DefDiagnostic::unresolved_proc_macro(module_id, loc.kind.clone(), loc.def.krate) } - _ => DefDiagnostic::macro_error(module_id, loc.kind, err.to_string()), + _ => DefDiagnostic::macro_error(module_id, loc.kind.clone(), err.to_string()), }; self.def_map.diagnostics.push(diag); } + if let errors @ [_, ..] = &*value { + let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id); + let diag = DefDiagnostic::macro_expansion_parse_error(module_id, loc.kind, &errors); + self.def_map.diagnostics.push(diag); + } // Then, fetch and process the item tree. This will reuse the expansion result from above. let item_tree = self.db.file_item_tree(file_id); + let mod_dir = self.mod_dirs[&module_id].clone(); ModCollector { def_collector: &mut *self, @@ -1384,8 +1422,9 @@ fn finish(mut self) -> DefMap { for directive in &self.unresolved_macros { match &directive.kind { MacroDirectiveKind::FnLike { ast_id, expand_to } => { + // FIXME: we shouldn't need to re-resolve the macro here just to get the unresolved error! let macro_call_as_call_id = macro_call_as_call_id( - self.db, + self.db.upcast(), ast_id, *expand_to, self.def_map.krate, @@ -1396,13 +1435,13 @@ fn finish(mut self) -> DefMap { directive.module_id, &path, BuiltinShadowMode::Module, + Some(MacroSubNs::Bang), ); resolved_res .resolved_def .take_macros() .map(|it| macro_id_to_def_id(self.db, it)) }, - &mut |_| (), ); if let Err(UnresolvedMacro { path }) = macro_call_as_call_id { self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( @@ -1489,6 +1528,7 @@ fn collect_in_top_module(&mut self, items: &[ModItem]) { fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { let krate = self.def_collector.def_map.krate; + let is_crate_root = self.module_id == DefMap::ROOT; // Note: don't assert that inserted value is fresh: it's simply not true // for macros. @@ -1496,28 +1536,22 @@ fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { // Prelude module is always considered to be `#[macro_use]`. if let Some(prelude_module) = self.def_collector.def_map.prelude { - if prelude_module.krate != krate { + if prelude_module.krate != krate && is_crate_root { cov_mark::hit!(prelude_is_macro_use); - self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); + self.def_collector.import_macros_from_extern_crate(prelude_module.krate, None); } } // This should be processed eagerly instead of deferred to resolving. // `#[macro_use] extern crate` is hoisted to imports macros before collecting // any other items. - for &item in items { - let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into()); - if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) { + // + // If we're not at the crate root, `macro_use`d extern crates are an error so let's just + // ignore them. + if is_crate_root { + for &item in items { if let ModItem::ExternCrate(id) = item { - let import = &self.item_tree[id]; - let attrs = self.item_tree.attrs( - self.def_collector.db, - krate, - ModItem::from(id).into(), - ); - if attrs.by_key("macro_use").exists() { - self.def_collector.import_macros_from_extern_crate(self.module_id, import); - } + self.process_macro_use_extern_crate(id); } } } @@ -1610,14 +1644,12 @@ fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { FunctionLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db); let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); - if self.def_collector.is_proc_macro && self.module_id == def_map.root { + if self.def_collector.is_proc_macro && self.module_id == DefMap::ROOT { if let Some(proc_macro) = attrs.parse_proc_macro_decl(&it.name) { - let crate_root = def_map.module_id(def_map.root); self.def_collector.export_proc_macro( proc_macro, ItemTreeId::new(self.tree_id, id), fn_id, - crate_root, ); } } @@ -1744,6 +1776,52 @@ fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { } } + fn process_macro_use_extern_crate(&mut self, extern_crate: FileItemTreeId) { + let db = self.def_collector.db; + let attrs = self.item_tree.attrs( + db, + self.def_collector.def_map.krate, + ModItem::from(extern_crate).into(), + ); + if let Some(cfg) = attrs.cfg() { + if !self.is_cfg_enabled(&cfg) { + return; + } + } + + let target_crate = + match self.def_collector.resolve_extern_crate(&self.item_tree[extern_crate].name) { + Some(m) => { + if m == self.def_collector.def_map.module_id(self.module_id) { + cov_mark::hit!(ignore_macro_use_extern_crate_self); + return; + } + m.krate + } + None => return, + }; + + cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); + + let mut single_imports = Vec::new(); + let hygiene = Hygiene::new_unhygienic(); + for attr in attrs.by_key("macro_use").attrs() { + let Some(paths) = attr.parse_path_comma_token_tree(db.upcast(), &hygiene) else { + // `#[macro_use]` (without any paths) found, forget collected names and just import + // all visible macros. + self.def_collector.import_macros_from_extern_crate(target_crate, None); + return; + }; + for path in paths { + if let Some(name) = path.as_ident() { + single_imports.push(name.clone()); + } + } + } + + self.def_collector.import_macros_from_extern_crate(target_crate, Some(single_imports)); + } + fn collect_module(&mut self, module_id: FileItemTreeId, attrs: &Attrs) { let path_attr = attrs.by_key("path").string_value(); let is_macro_use = attrs.by_key("macro_use").exists(); @@ -1844,7 +1922,6 @@ fn push_child_module( let vis = def_map .resolve_visibility(self.def_collector.db, self.module_id, visibility, false) .unwrap_or(Visibility::Public); - let modules = &mut def_map.modules; let origin = match definition { None => ModuleOrigin::Inline { definition: declaration, @@ -1858,6 +1935,7 @@ fn push_child_module( }, }; + let modules = &mut def_map.modules; let res = modules.alloc(ModuleData::new(origin, vis)); modules[res].parent = Some(self.module_id); for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { @@ -1919,7 +1997,10 @@ fn resolve_attributes( if self.def_collector.def_map.is_builtin_or_registered_attr(&attr.path) { continue; } - tracing::debug!("non-builtin attribute {}", attr.path); + tracing::debug!( + "non-builtin attribute {}", + attr.path.display(self.def_collector.db.upcast()) + ); let ast_id = AstIdWithPath::new( self.file_id(), @@ -2053,8 +2134,8 @@ fn collect_macro_def(&mut self, id: FileItemTreeId, module: ModuleId) stdx::always!( name == mac.name, "built-in macro {} has #[rustc_builtin_macro] which declares different name {}", - mac.name, - name + mac.name.display(self.def_collector.db.upcast()), + name.display(self.def_collector.db.upcast()) ); helpers_opt = Some(helpers); } @@ -2089,73 +2170,60 @@ fn collect_macro_def(&mut self, id: FileItemTreeId, module: ModuleId) &self.item_tree[mac.visibility], ); if let Some(helpers) = helpers_opt { - self.def_collector - .def_map - .exported_derives - .insert(macro_id_to_def_id(self.def_collector.db, macro_id.into()), helpers); + if self.def_collector.def_map.block.is_none() { + Arc::get_mut(&mut self.def_collector.def_map.data) + .unwrap() + .exported_derives + .insert(macro_id_to_def_id(self.def_collector.db, macro_id.into()), helpers); + } } } fn collect_macro_call(&mut self, mac: &MacroCall, container: ItemContainerId) { let ast_id = AstIdWithPath::new(self.file_id(), mac.ast_id, ModPath::clone(&mac.path)); + let db = self.def_collector.db; - // Case 1: try to resolve in legacy scope and expand macro_rules - let mut error = None; - match macro_call_as_call_id( - self.def_collector.db, + // FIXME: Immediately expanding in "Case 1" is insufficient since "Case 2" may also define + // new legacy macros that create textual scopes. We need a way to resolve names in textual + // scopes without eager expansion. + + // Case 1: try to resolve macro calls with single-segment name and expand macro_rules + if let Ok(res) = macro_call_as_call_id( + db.upcast(), &ast_id, mac.expand_to, self.def_collector.def_map.krate, |path| { path.as_ident().and_then(|name| { - self.def_collector.def_map.with_ancestor_maps( - self.def_collector.db, - self.module_id, - &mut |map, module| { - map[module] - .scope - .get_legacy_macro(name) - .and_then(|it| it.last()) - .map(|&it| macro_id_to_def_id(self.def_collector.db, it)) - }, - ) + let def_map = &self.def_collector.def_map; + def_map + .with_ancestor_maps(db, self.module_id, &mut |map, module| { + map[module].scope.get_legacy_macro(name)?.last().copied() + }) + .or_else(|| def_map[self.module_id].scope.get(name).take_macros()) + .or_else(|| def_map.macro_use_prelude.get(name).copied()) + .filter(|&id| { + sub_namespace_match( + Some(MacroSubNs::from_id(db, id)), + Some(MacroSubNs::Bang), + ) + }) + .map(|it| macro_id_to_def_id(self.def_collector.db, it)) }) }, - &mut |err| { - error.get_or_insert(err); - }, ) { - Ok(Ok(macro_call_id)) => { - // Legacy macros need to be expanded immediately, so that any macros they produce - // are in scope. + // Legacy macros need to be expanded immediately, so that any macros they produce + // are in scope. + if let Some(val) = res { self.def_collector.collect_macro_expansion( self.module_id, - macro_call_id, + val, self.macro_depth + 1, container, ); - - if let Some(err) = error { - self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error( - self.module_id, - MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to }, - err.to_string(), - )); - } - - return; } - Ok(Err(_)) => { - // Built-in macro failed eager expansion. - self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error( - self.module_id, - MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to }, - error.unwrap().to_string(), - )); - return; - } - Err(UnresolvedMacro { .. }) => (), + return; } // Case 2: resolve in module scope, expand during name resolution. @@ -2215,10 +2283,11 @@ fn do_collect_defs(db: &dyn DefDatabase, def_map: DefMap) -> DefMap { unresolved_macros: Vec::new(), mod_dirs: FxHashMap::default(), cfg_options: &CfgOptions::default(), - proc_macros: Default::default(), + proc_macros: Ok(vec![]), from_glob_import: Default::default(), skip_attrs: Default::default(), is_proc_macro: false, + hygienes: FxHashMap::default(), }; collector.seed_with_top_level(); collector.collect(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs index b024d7c6777c..18b424255cd6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs @@ -4,7 +4,10 @@ use cfg::{CfgExpr, CfgOptions}; use hir_expand::{attrs::AttrId, MacroCallKind}; use la_arena::Idx; -use syntax::ast::{self, AnyHasAttrs}; +use syntax::{ + ast::{self, AnyHasAttrs}, + SyntaxError, +}; use crate::{ item_tree::{self, ItemTreeId}, @@ -29,11 +32,15 @@ pub enum DefDiagnosticKind { MacroError { ast: MacroCallKind, message: String }, + MacroExpansionParseError { ast: MacroCallKind, errors: Box<[SyntaxError]> }, + UnimplementedBuiltinMacro { ast: AstId }, InvalidDeriveTarget { ast: AstId, id: usize }, MalformedDerive { ast: AstId, id: usize }, + + MacroDefError { ast: AstId, message: String }, } #[derive(Debug, PartialEq, Eq)] @@ -81,7 +88,8 @@ pub fn unconfigured_code( Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } } } - pub(super) fn unresolved_proc_macro( + // FIXME: Whats the difference between this and unresolved_macro_call + pub(crate) fn unresolved_proc_macro( container: LocalModuleId, ast: MacroCallKind, krate: CrateId, @@ -89,7 +97,7 @@ pub(super) fn unresolved_proc_macro( Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } } } - pub(super) fn macro_error( + pub(crate) fn macro_error( container: LocalModuleId, ast: MacroCallKind, message: String, @@ -97,7 +105,22 @@ pub(super) fn macro_error( Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } } } - pub(super) fn unresolved_macro_call( + pub(crate) fn macro_expansion_parse_error( + container: LocalModuleId, + ast: MacroCallKind, + errors: &[SyntaxError], + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::MacroExpansionParseError { + ast, + errors: errors.to_vec().into_boxed_slice(), + }, + } + } + + // FIXME: Whats the difference between this and unresolved_proc_macro + pub(crate) fn unresolved_macro_call( container: LocalModuleId, ast: MacroCallKind, path: ModPath, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs index 51c565fe1233..2dcc2c30fe16 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs @@ -74,12 +74,20 @@ pub(super) fn resolve_declaration( candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) } None if file_id.is_include_macro(db.upcast()) => { - candidate_files.push(format!("{name}.rs")); - candidate_files.push(format!("{name}/mod.rs")); + candidate_files.push(format!("{}.rs", name.display(db.upcast()))); + candidate_files.push(format!("{}/mod.rs", name.display(db.upcast()))); } None => { - candidate_files.push(format!("{}{name}.rs", self.dir_path.0)); - candidate_files.push(format!("{}{name}/mod.rs", self.dir_path.0)); + candidate_files.push(format!( + "{}{}.rs", + self.dir_path.0, + name.display(db.upcast()) + )); + candidate_files.push(format!( + "{}{}/mod.rs", + self.dir_path.0, + name.display(db.upcast()) + )); } }; @@ -91,7 +99,7 @@ pub(super) fn resolve_declaration( let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() { (DirPath::empty(), false) } else { - (DirPath::new(format!("{name}/")), true) + (DirPath::new(format!("{}/", name.display(db.upcast()))), true) }; if let Some(mod_dir) = self.child(dir_path, root_non_dir_owner) { return Ok((file_id, is_mod_rs, mod_dir)); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs index 25478481dd0b..5f6163175a72 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs @@ -16,11 +16,11 @@ use crate::{ db::DefDatabase, item_scope::BUILTIN_SCOPE, - nameres::{BuiltinShadowMode, DefMap}, + nameres::{sub_namespace_match, BuiltinShadowMode, DefMap, MacroSubNs}, path::{ModPath, PathKind}, per_ns::PerNs, visibility::{RawVisibility, Visibility}, - AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, ModuleId, + AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -58,18 +58,22 @@ fn with( } } -impl DefMap { - pub(super) fn resolve_name_in_extern_prelude( - &self, +impl PerNs { + pub(super) fn filter_macro( + mut self, db: &dyn DefDatabase, - name: &Name, - ) -> Option { - match self.block { - Some(_) => self.crate_root(db).def_map(db).extern_prelude.get(name).copied(), - None => self.extern_prelude.get(name).copied(), - } - } + expected: Option, + ) -> Self { + self.macros = self.macros.filter(|&(id, _)| { + let this = MacroSubNs::from_id(db, id); + sub_namespace_match(Some(this), expected) + }); + self + } +} + +impl DefMap { pub(crate) fn resolve_visibility( &self, db: &dyn DefDatabase, @@ -83,7 +87,7 @@ pub(crate) fn resolve_visibility( let mut vis = match visibility { RawVisibility::Module(path) => { let (result, remaining) = - self.resolve_path(db, original_module, path, BuiltinShadowMode::Module); + self.resolve_path(db, original_module, path, BuiltinShadowMode::Module, None); if remaining.is_some() { return None; } @@ -106,7 +110,7 @@ pub(crate) fn resolve_visibility( // ...unless we're resolving visibility for an associated item in an impl. if self.block_id() != m.block && !within_impl { cov_mark::hit!(adjust_vis_in_block_def_map); - vis = Visibility::Module(self.module_id(self.root())); + vis = Visibility::Module(self.module_id(Self::ROOT)); tracing::debug!("visibility {:?} points outside DefMap, adjusting to {:?}", m, vis); } } @@ -124,6 +128,9 @@ pub(super) fn resolve_path_fp_with_macro( mut original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, + // Pass `MacroSubNs` if we know we're resolving macro names and which kind of macro we're + // resolving them to. Pass `None` otherwise, e.g. when we're resolving import paths. + expected_macro_subns: Option, ) -> ResolvePathResult { let mut result = ResolvePathResult::empty(ReachedFixedPoint::No); @@ -136,6 +143,7 @@ pub(super) fn resolve_path_fp_with_macro( original_module, path, shadow, + expected_macro_subns, ); // Merge `new` into `result`. @@ -154,7 +162,7 @@ pub(super) fn resolve_path_fp_with_macro( match ¤t_map.block { Some(block) => { original_module = block.parent.local_id; - arc = block.parent.def_map(db); + arc = block.parent.def_map(db, current_map.krate); current_map = &*arc; } None => return result, @@ -169,11 +177,15 @@ pub(super) fn resolve_path_fp_with_macro_single( original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, + expected_macro_subns: Option, ) -> ResolvePathResult { let graph = db.crate_graph(); let _cx = stdx::panic_context::enter(format!( - "DefMap {:?} crate_name={:?} block={:?} path={path}", - self.krate, graph[self.krate].display_name, self.block + "DefMap {:?} crate_name={:?} block={:?} path={}", + self.krate, + graph[self.krate].display_name, + self.block, + path.display(db.upcast()) )); let mut segments = path.segments().iter().enumerate(); @@ -181,21 +193,21 @@ pub(super) fn resolve_path_fp_with_macro_single( PathKind::DollarCrate(krate) => { if krate == self.krate { cov_mark::hit!(macro_dollar_crate_self); - PerNs::types(self.crate_root(db).into(), Visibility::Public) + PerNs::types(self.crate_root().into(), Visibility::Public) } else { let def_map = db.crate_def_map(krate); - let module = def_map.module_id(def_map.root); + let module = def_map.module_id(Self::ROOT); cov_mark::hit!(macro_dollar_crate_other); PerNs::types(module.into(), Visibility::Public) } } - PathKind::Crate => PerNs::types(self.crate_root(db).into(), Visibility::Public), + PathKind::Crate => PerNs::types(self.crate_root().into(), Visibility::Public), // plain import or absolute path in 2015: crate-relative with // fallback to extern prelude (with the simplification in // rust-lang/rust#57745) // FIXME there must be a nicer way to write this condition PathKind::Plain | PathKind::Abs - if self.edition == Edition::Edition2015 + if self.data.edition == Edition::Edition2015 && (path.kind == PathKind::Abs || mode == ResolveMode::Import) => { let (_, segment) = match segments.next() { @@ -220,7 +232,13 @@ pub(super) fn resolve_path_fp_with_macro_single( if path.segments().len() == 1 { shadow } else { BuiltinShadowMode::Module }; tracing::debug!("resolving {:?} in module", segment); - self.resolve_name_in_module(db, original_module, segment, prefer_module) + self.resolve_name_in_module( + db, + original_module, + segment, + prefer_module, + expected_macro_subns, + ) } PathKind::Super(lvl) => { let mut module = original_module; @@ -236,16 +254,20 @@ pub(super) fn resolve_path_fp_with_macro_single( ); tracing::debug!( "`super` path: {} -> {} in parent map", - path, - new_path - ); - return block.parent.def_map(db).resolve_path_fp_with_macro( - db, - mode, - block.parent.local_id, - &new_path, - shadow, + path.display(db.upcast()), + new_path.display(db.upcast()) ); + return block + .parent + .def_map(db, self.krate) + .resolve_path_fp_with_macro( + db, + mode, + block.parent.local_id, + &new_path, + shadow, + expected_macro_subns, + ); } None => { tracing::debug!("super path in root module"); @@ -271,7 +293,7 @@ pub(super) fn resolve_path_fp_with_macro_single( Some((_, segment)) => segment, None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), }; - if let Some(&def) = self.extern_prelude.get(segment) { + if let Some(&def) = self.data.extern_prelude.get(segment) { tracing::debug!("absolute path {:?} resolved to crate {:?}", path, def); PerNs::types(def.into(), Visibility::Public) } else { @@ -303,7 +325,12 @@ pub(super) fn resolve_path_fp_with_macro_single( ); tracing::debug!("resolving {:?} in other crate", path); let defp_map = module.def_map(db); - let (def, s) = defp_map.resolve_path(db, module.local_id, &path, shadow); + // Macro sub-namespaces only matter when resolving single-segment paths + // because `macro_use` and other preludes should be taken into account. At + // this point, we know we're resolving a multi-segment path so macro kind + // expectation is discarded. + let (def, s) = + defp_map.resolve_path(db, module.local_id, &path, shadow, None); return ResolvePathResult::with( def, ReachedFixedPoint::Yes, @@ -331,11 +358,11 @@ pub(super) fn resolve_path_fp_with_macro_single( Some(local_id) => { let variant = EnumVariantId { parent: e, local_id }; match &*enum_data.variants[local_id].variant_data { - crate::adt::VariantData::Record(_) => { + crate::data::adt::VariantData::Record(_) => { PerNs::types(variant.into(), Visibility::Public) } - crate::adt::VariantData::Tuple(_) - | crate::adt::VariantData::Unit => { + crate::data::adt::VariantData::Tuple(_) + | crate::data::adt::VariantData::Unit => { PerNs::both(variant.into(), variant.into(), Visibility::Public) } } @@ -381,19 +408,24 @@ fn resolve_name_in_module( module: LocalModuleId, name: &Name, shadow: BuiltinShadowMode, + expected_macro_subns: Option, ) -> PerNs { // Resolve in: // - legacy scope of macro // - current module / scope - // - extern prelude + // - extern prelude / macro_use prelude // - std prelude let from_legacy_macro = self[module] .scope .get_legacy_macro(name) // FIXME: shadowing .and_then(|it| it.last()) - .map_or_else(PerNs::none, |&m| PerNs::macros(m, Visibility::Public)); - let from_scope = self[module].scope.get(name); + .copied() + .filter(|&id| { + sub_namespace_match(Some(MacroSubNs::from_id(db, id)), expected_macro_subns) + }) + .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public)); + let from_scope = self[module].scope.get(name).filter_macro(db, expected_macro_subns); let from_builtin = match self.block { Some(_) => { // Only resolve to builtins in the root `DefMap`. @@ -410,13 +442,27 @@ fn resolve_name_in_module( }; let extern_prelude = || { - self.extern_prelude + if self.block.is_some() { + // Don't resolve extern prelude in block `DefMap`s. + return PerNs::none(); + } + self.data + .extern_prelude .get(name) .map_or(PerNs::none(), |&it| PerNs::types(it.into(), Visibility::Public)) }; + let macro_use_prelude = || { + self.macro_use_prelude + .get(name) + .map_or(PerNs::none(), |&it| PerNs::macros(it.into(), Visibility::Public)) + }; let prelude = || self.resolve_in_prelude(db, name); - from_legacy_macro.or(from_scope_or_builtin).or_else(extern_prelude).or_else(prelude) + from_legacy_macro + .or(from_scope_or_builtin) + .or_else(extern_prelude) + .or_else(macro_use_prelude) + .or_else(prelude) } fn resolve_name_in_crate_root_or_extern_prelude( @@ -426,13 +472,20 @@ fn resolve_name_in_crate_root_or_extern_prelude( ) -> PerNs { let from_crate_root = match self.block { Some(_) => { - let def_map = self.crate_root(db).def_map(db); - def_map[def_map.root].scope.get(name) + let def_map = self.crate_root().def_map(db); + def_map[Self::ROOT].scope.get(name) } - None => self[self.root].scope.get(name), + None => self[Self::ROOT].scope.get(name), }; let from_extern_prelude = || { - self.resolve_name_in_extern_prelude(db, name) + if self.block.is_some() { + // Don't resolve extern prelude in block `DefMap`s. + return PerNs::none(); + } + self.data + .extern_prelude + .get(name) + .copied() .map_or(PerNs::none(), |it| PerNs::types(it.into(), Visibility::Public)) }; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs index caad4a1f3817..751b7beaac15 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs @@ -52,7 +52,7 @@ pub fn parse_proc_macro_decl(&self, func_name: &Name) -> Option { } // This fn is intended for `#[proc_macro_derive(..)]` and `#[rustc_builtin_macro(..)]`, which have -// the same strucuture. +// the same structure. #[rustfmt::skip] pub(crate) fn parse_macro_name_and_helper_attrs(tt: &[TokenTree]) -> Option<(Name, Box<[Name]>)> { match tt { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs index 8a27c60df5c2..dd7c3c363062 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -4,10 +4,9 @@ mod mod_resolution; mod primitives; -use std::sync::Arc; - use base_db::{fixture::WithFixture, SourceDatabase}; use expect_test::{expect, Expect}; +use triomphe::Arc; use crate::{db::DefDatabase, test_db::TestDB}; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index 13e6825f8210..4931c36bbca9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -1,8 +1,7 @@ -use std::sync::Arc; - use base_db::SourceDatabaseExt; +use triomphe::Arc; -use crate::{AdtId, ModuleDefId}; +use crate::{db::DefDatabase, AdtId, ModuleDefId}; use super::*; @@ -15,7 +14,7 @@ fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: }); assert!(format!("{events:?}").contains("crate_def_map"), "{events:#?}") } - db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string())); + db.set_file_text(pos.file_id, Arc::from(ra_fixture_change)); { let events = db.log_executed(|| { @@ -96,7 +95,7 @@ fn f() { }); assert!(format!("{events:?}").contains("crate_def_map"), "{events:#?}") } - db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string())); + db.set_file_text(pos.file_id, Arc::from("m!(Y);")); { let events = db.log_executed(|| { @@ -109,7 +108,7 @@ fn f() { } #[test] -fn typing_inside_a_function_should_not_invalidate_expansions() { +fn typing_inside_a_function_should_not_invalidate_item_expansions() { let (mut db, pos) = TestDB::with_position( r#" //- /lib.rs @@ -140,7 +139,7 @@ fn quux() { 1$0 } let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); assert_eq!(n_recalculated_item_trees, 6); let n_reparsed_macros = - events.iter().filter(|it| it.contains("parse_macro_expansion")).count(); + events.iter().filter(|it| it.contains("parse_macro_expansion(")).count(); assert_eq!(n_reparsed_macros, 3); } @@ -150,7 +149,7 @@ fn quux() { 92 } m!(Y); m!(Z); "#; - db.set_file_text(pos.file_id, Arc::new(new_text.to_string())); + db.set_file_text(pos.file_id, Arc::from(new_text)); { let events = db.log_executed(|| { @@ -161,7 +160,7 @@ fn quux() { 92 } let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); assert_eq!(n_recalculated_item_trees, 1); let n_reparsed_macros = - events.iter().filter(|it| it.contains("parse_macro_expansion")).count(); + events.iter().filter(|it| it.contains("parse_macro_expansion(")).count(); assert_eq!(n_reparsed_macros, 0); } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs index a4ccd14cbb46..f4cca8d68d0a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -259,6 +259,72 @@ macro_rules! structs_priv { ); } +#[test] +fn macro_use_filter() { + check( + r#" +//- /main.rs crate:main deps:empty,multiple,all +#[macro_use()] +extern crate empty; + +foo_not_imported!(); + +#[macro_use(bar1)] +#[macro_use()] +#[macro_use(bar2, bar3)] +extern crate multiple; + +bar1!(); +bar2!(); +bar3!(); +bar_not_imported!(); + +#[macro_use(baz1)] +#[macro_use] +#[macro_use(baz2)] +extern crate all; + +baz1!(); +baz2!(); +baz3!(); + +//- /empty.rs crate:empty +#[macro_export] +macro_rules! foo_not_imported { () => { struct NotOkFoo; } } + +//- /multiple.rs crate:multiple +#[macro_export] +macro_rules! bar1 { () => { struct OkBar1; } } +#[macro_export] +macro_rules! bar2 { () => { struct OkBar2; } } +#[macro_export] +macro_rules! bar3 { () => { struct OkBar3; } } +#[macro_export] +macro_rules! bar_not_imported { () => { struct NotOkBar; } } + +//- /all.rs crate:all +#[macro_export] +macro_rules! baz1 { () => { struct OkBaz1; } } +#[macro_export] +macro_rules! baz2 { () => { struct OkBaz2; } } +#[macro_export] +macro_rules! baz3 { () => { struct OkBaz3; } } +"#, + expect![[r#" + crate + OkBar1: t v + OkBar2: t v + OkBar3: t v + OkBaz1: t v + OkBaz2: t v + OkBaz3: t v + all: t + empty: t + multiple: t + "#]], + ); +} + #[test] fn prelude_is_macro_use() { cov_mark::check!(prelude_is_macro_use); @@ -664,6 +730,29 @@ macro_rules! foo { ); } +#[test] +fn macro_dollar_crate_is_correct_in_derive_meta() { + let map = compute_crate_def_map( + r#" +//- minicore: derive, clone +//- /main.rs crate:main deps:lib +lib::foo!(); + +//- /lib.rs crate:lib +#[macro_export] +macro_rules! foo { + () => { + #[derive($crate::Clone)] + struct S; + } +} + +pub use core::clone::Clone; +"#, + ); + assert_eq!(map.modules[DefMap::ROOT].scope.impls().len(), 1); +} + #[test] fn expand_derive() { let map = compute_crate_def_map( @@ -683,7 +772,7 @@ fn expand_derive() { pub macro Clone {} "#, ); - assert_eq!(map.modules[map.root].scope.impls().len(), 2); + assert_eq!(map.modules[DefMap::ROOT].scope.impls().len(), 2); } #[test] @@ -726,7 +815,7 @@ fn builtin_derive_with_unresolved_attributes_fall_back() { pub macro Clone {} "#, ); - assert_eq!(map.modules[map.root].scope.impls().len(), 1); + assert_eq!(map.modules[DefMap::ROOT].scope.impls().len(), 1); } #[test] @@ -991,7 +1080,7 @@ macro_rules! mbe { #[test] fn collects_derive_helpers() { - let def_map = compute_crate_def_map( + let db = TestDB::with_files( r#" #![crate_type="proc-macro"] struct TokenStream; @@ -1002,11 +1091,13 @@ pub fn derive_macro_2(_item: TokenStream) -> TokenStream { } "#, ); + let krate = db.crate_graph().iter().next().unwrap(); + let def_map = db.crate_def_map(krate); - assert_eq!(def_map.exported_derives.len(), 1); - match def_map.exported_derives.values().next() { + assert_eq!(def_map.data.exported_derives.len(), 1); + match def_map.data.exported_derives.values().next() { Some(helpers) => match &**helpers { - [attr] => assert_eq!(attr.to_string(), "helper_attr"), + [attr] => assert_eq!(attr.display(&db).to_string(), "helper_attr"), _ => unreachable!(), }, _ => unreachable!(), @@ -1169,7 +1260,7 @@ macro_rules! n { #[test] fn macro_use_imports_all_macro_types() { - let def_map = compute_crate_def_map( + let db = TestDB::with_files( r#" //- /main.rs crate:main deps:lib #[macro_use] @@ -1192,18 +1283,153 @@ macro_rules! legacy { () => () } fn proc_attr(a: TokenStream, b: TokenStream) -> TokenStream { a } "#, ); + let krate = db.crate_graph().iter().next().unwrap(); + let def_map = db.crate_def_map(krate); - let root = &def_map[def_map.root()].scope; - let actual = root - .legacy_macros() - .sorted_by(|a, b| std::cmp::Ord::cmp(&a.0, &b.0)) - .map(|(name, _)| format!("{name}\n")) - .collect::(); + let root_module = &def_map[DefMap::ROOT].scope; + assert!( + root_module.legacy_macros().count() == 0, + "`#[macro_use]` shouldn't bring macros into textual macro scope", + ); + + let actual = def_map + .macro_use_prelude + .iter() + .map(|(name, _)| name.display(&db).to_string()) + .sorted() + .join("\n"); expect![[r#" legacy macro20 - proc_attr - "#]] + proc_attr"#]] .assert_eq(&actual); } + +#[test] +fn non_prelude_macros_take_precedence_over_macro_use_prelude() { + check( + r#" +//- /lib.rs edition:2021 crate:lib deps:dep,core +#[macro_use] +extern crate dep; + +macro foo() { struct Ok; } +macro bar() { fn ok() {} } + +foo!(); +bar!(); + +//- /dep.rs crate:dep +#[macro_export] +macro_rules! foo { + () => { struct NotOk; } +} + +//- /core.rs crate:core +pub mod prelude { + pub mod rust_2021 { + #[macro_export] + macro_rules! bar { + () => { fn not_ok() {} } + } + } +} + "#, + expect![[r#" + crate + Ok: t v + bar: m + dep: t + foo: m + ok: v + "#]], + ); +} + +#[test] +fn macro_use_prelude_is_eagerly_expanded() { + // See FIXME in `ModCollector::collect_macro_call()`. + check( + r#" +//- /main.rs crate:main deps:lib +#[macro_use] +extern crate lib; +mk_foo!(); +mod a { + foo!(); +} +//- /lib.rs crate:lib +#[macro_export] +macro_rules! mk_foo { + () => { + macro_rules! foo { + () => { struct Ok; } + } + } +} + "#, + expect![[r#" + crate + a: t + lib: t + + crate::a + Ok: t v + "#]], + ); +} + +#[test] +fn macro_sub_namespace() { + let map = compute_crate_def_map( + r#" +//- minicore: derive, clone +macro_rules! Clone { () => {} } +macro_rules! derive { () => {} } + +#[derive(Clone)] +struct S; + "#, + ); + assert_eq!(map.modules[DefMap::ROOT].scope.impls().len(), 1); +} + +#[test] +fn macro_sub_namespace2() { + check( + r#" +//- /main.rs edition:2021 crate:main deps:proc,core +use proc::{foo, bar}; + +foo!(); +bar!(); + +//- /proc.rs crate:proc +#![crate_type="proc-macro"] +#[proc_macro_derive(foo)] +pub fn foo() {} +#[proc_macro_attribute] +pub fn bar() {} + +//- /core.rs crate:core +pub mod prelude { + pub mod rust_2021 { + pub macro foo() { + struct Ok; + } + pub macro bar() { + fn ok() {} + } + } +} + "#, + expect![[r#" + crate + Ok: t v + bar: m + foo: m + ok: v + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs index a01931288478..81bc0ff91e3a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs @@ -887,7 +887,7 @@ fn cfg_in_module_file() { //- /module.rs #![cfg(NEVER)] -struct AlsoShoulntAppear; +struct AlsoShouldNotAppear; "#, expect![[r#" crate diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/path.rs index f3197d1800f2..b9b808254971 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs @@ -7,15 +7,14 @@ }; use crate::{ - body::LowerCtx, - type_ref::{ConstRefOrPath, LifetimeRef}, + lang_item::LangItemTarget, + lower::LowerCtx, + type_ref::{ConstRefOrPath, LifetimeRef, TypeBound, TypeRef}, }; use hir_expand::name::Name; use intern::Interned; use syntax::ast; -use crate::type_ref::{TypeBound, TypeRef}; - pub use hir_expand::mod_path::{path, ModPath, PathKind}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -36,13 +35,19 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Path { - /// Type based path like `::foo`. - /// Note that paths like `::foo` are desugared to `Trait::::foo`. - type_anchor: Option>, - mod_path: Interned, - /// Invariant: the same len as `self.mod_path.segments` or `None` if all segments are `None`. - generic_args: Option>]>>, +pub enum Path { + /// A normal path + Normal { + /// Type based path like `::foo`. + /// Note that paths like `::foo` are desugared to `Trait::::foo`. + type_anchor: Option>, + mod_path: Interned, + /// Invariant: the same len as `self.mod_path.segments` or `None` if all segments are `None`. + generic_args: Option>]>>, + }, + /// A link to a lang item. It is used in desugaring of things like `x?`. We can show these + /// links via a normal path since they might be private and not accessible in the usage place. + LangItem(LangItemTarget), } /// Generic arguments to a path segment (e.g. the `i32` in `Option`). This @@ -102,51 +107,77 @@ pub fn from_known_path( ) -> Path { let generic_args = generic_args.into(); assert_eq!(path.len(), generic_args.len()); - Path { type_anchor: None, mod_path: Interned::new(path), generic_args: Some(generic_args) } + Path::Normal { + type_anchor: None, + mod_path: Interned::new(path), + generic_args: Some(generic_args), + } + } + + /// Converts a known mod path to `Path`. + pub fn from_known_path_with_no_generic(path: ModPath) -> Path { + Path::Normal { type_anchor: None, mod_path: Interned::new(path), generic_args: None } } pub fn kind(&self) -> &PathKind { - &self.mod_path.kind + match self { + Path::Normal { mod_path, .. } => &mod_path.kind, + Path::LangItem(_) => &PathKind::Abs, + } } pub fn type_anchor(&self) -> Option<&TypeRef> { - self.type_anchor.as_deref() + match self { + Path::Normal { type_anchor, .. } => type_anchor.as_deref(), + Path::LangItem(_) => None, + } } pub fn segments(&self) -> PathSegments<'_> { - let s = PathSegments { - segments: self.mod_path.segments(), - generic_args: self.generic_args.as_deref(), + let Path::Normal { mod_path, generic_args, .. } = self else { + return PathSegments { + segments: &[], + generic_args: None, + }; }; + let s = + PathSegments { segments: mod_path.segments(), generic_args: generic_args.as_deref() }; if let Some(generic_args) = s.generic_args { assert_eq!(s.segments.len(), generic_args.len()); } s } - pub fn mod_path(&self) -> &ModPath { - &self.mod_path + pub fn mod_path(&self) -> Option<&ModPath> { + match self { + Path::Normal { mod_path, .. } => Some(&mod_path), + Path::LangItem(_) => None, + } } pub fn qualifier(&self) -> Option { - if self.mod_path.is_ident() { + let Path::Normal { mod_path, generic_args, type_anchor } = self else { + return None; + }; + if mod_path.is_ident() { return None; } - let res = Path { - type_anchor: self.type_anchor.clone(), + let res = Path::Normal { + type_anchor: type_anchor.clone(), mod_path: Interned::new(ModPath::from_segments( - self.mod_path.kind, - self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(), + mod_path.kind, + mod_path.segments()[..mod_path.segments().len() - 1].iter().cloned(), )), - generic_args: self.generic_args.as_ref().map(|it| it[..it.len() - 1].to_vec().into()), + generic_args: generic_args.as_ref().map(|it| it[..it.len() - 1].to_vec().into()), }; Some(res) } pub fn is_self_type(&self) -> bool { - self.type_anchor.is_none() - && self.generic_args.as_deref().is_none() - && self.mod_path.is_Self() + let Path::Normal { mod_path, generic_args, type_anchor } = self else { + return false; + }; + type_anchor.is_none() && generic_args.as_deref().is_none() && mod_path.is_Self() } } @@ -222,7 +253,7 @@ pub(crate) fn empty() -> GenericArgs { impl From for Path { fn from(name: Name) -> Path { - Path { + Path::Normal { type_anchor: None, mod_path: Interned::new(ModPath::from_segments(PathKind::Plain, iter::once(name))), generic_args: None, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs index b7542bd777d0..26d2706175ca 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs @@ -2,17 +2,15 @@ use std::iter; -use crate::type_ref::ConstRefOrPath; +use crate::{lower::LowerCtx, type_ref::ConstRefOrPath}; use either::Either; use hir_expand::name::{name, AsName}; use intern::Interned; use syntax::ast::{self, AstNode, HasTypeBounds}; -use super::AssociatedTypeBinding; use crate::{ - body::LowerCtx, - path::{GenericArg, GenericArgs, ModPath, Path, PathKind}, + path::{AssociatedTypeBinding, GenericArg, GenericArgs, ModPath, Path, PathKind}, type_ref::{LifetimeRef, TypeBound, TypeRef}, }; @@ -75,8 +73,11 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option>::Foo desugars to Trait::Foo Some(trait_ref) => { - let Path { mod_path, generic_args: path_generic_args, .. } = - Path::from_src(trait_ref.path()?, ctx)?; + let Path::Normal { mod_path, generic_args: path_generic_args, .. } = + Path::from_src(trait_ref.path()?, ctx)? else + { + return None; + }; let num_segments = mod_path.segments().len(); kind = mod_path.kind; @@ -157,7 +158,7 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option { + if assoc_type_arg.param_list().is_some() { + // We currently ignore associated return type bounds. + continue; + } if let Some(name_ref) = assoc_type_arg.name_ref() { let name = name_ref.as_name(); let args = assoc_type_arg diff --git a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs index 2d45c8c8da1a..0aead6f37f73 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Write}; -use hir_expand::mod_path::PathKind; +use hir_expand::{db::ExpandDatabase, mod_path::PathKind}; use intern::Interned; use itertools::Itertools; @@ -11,11 +11,14 @@ type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef}, }; -pub(crate) fn print_path(path: &Path, buf: &mut dyn Write) -> fmt::Result { +pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { + if let Path::LangItem(x) = path { + return write!(buf, "$lang_item::{x:?}"); + } match path.type_anchor() { Some(anchor) => { write!(buf, "<")?; - print_type_ref(anchor, buf)?; + print_type_ref(db, anchor, buf)?; write!(buf, ">::")?; } None => match path.kind() { @@ -41,10 +44,10 @@ pub(crate) fn print_path(path: &Path, buf: &mut dyn Write) -> fmt::Result { write!(buf, "::")?; } - write!(buf, "{}", segment.name)?; + write!(buf, "{}", segment.name.display(db))?; if let Some(generics) = segment.args_and_bindings { write!(buf, "::<")?; - print_generic_args(generics, buf)?; + print_generic_args(db, generics, buf)?; write!(buf, ">")?; } @@ -53,12 +56,16 @@ pub(crate) fn print_path(path: &Path, buf: &mut dyn Write) -> fmt::Result { Ok(()) } -pub(crate) fn print_generic_args(generics: &GenericArgs, buf: &mut dyn Write) -> fmt::Result { +pub(crate) fn print_generic_args( + db: &dyn ExpandDatabase, + generics: &GenericArgs, + buf: &mut dyn Write, +) -> fmt::Result { let mut first = true; let args = if generics.has_self_type { let (self_ty, args) = generics.args.split_first().unwrap(); write!(buf, "Self=")?; - print_generic_arg(self_ty, buf)?; + print_generic_arg(db, self_ty, buf)?; first = false; args } else { @@ -69,35 +76,43 @@ pub(crate) fn print_generic_args(generics: &GenericArgs, buf: &mut dyn Write) -> write!(buf, ", ")?; } first = false; - print_generic_arg(arg, buf)?; + print_generic_arg(db, arg, buf)?; } for binding in generics.bindings.iter() { if !first { write!(buf, ", ")?; } first = false; - write!(buf, "{}", binding.name)?; + write!(buf, "{}", binding.name.display(db))?; if !binding.bounds.is_empty() { write!(buf, ": ")?; - print_type_bounds(&binding.bounds, buf)?; + print_type_bounds(db, &binding.bounds, buf)?; } if let Some(ty) = &binding.type_ref { write!(buf, " = ")?; - print_type_ref(ty, buf)?; + print_type_ref(db, ty, buf)?; } } Ok(()) } -pub(crate) fn print_generic_arg(arg: &GenericArg, buf: &mut dyn Write) -> fmt::Result { +pub(crate) fn print_generic_arg( + db: &dyn ExpandDatabase, + arg: &GenericArg, + buf: &mut dyn Write, +) -> fmt::Result { match arg { - GenericArg::Type(ty) => print_type_ref(ty, buf), - GenericArg::Const(c) => write!(buf, "{c}"), - GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name), + GenericArg::Type(ty) => print_type_ref(db, ty, buf), + GenericArg::Const(c) => write!(buf, "{}", c.display(db)), + GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db)), } } -pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Result { +pub(crate) fn print_type_ref( + db: &dyn ExpandDatabase, + type_ref: &TypeRef, + buf: &mut dyn Write, +) -> fmt::Result { // FIXME: deduplicate with `HirDisplay` impl match type_ref { TypeRef::Never => write!(buf, "!")?, @@ -108,18 +123,18 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re if i != 0 { write!(buf, ", ")?; } - print_type_ref(field, buf)?; + print_type_ref(db, field, buf)?; } write!(buf, ")")?; } - TypeRef::Path(path) => print_path(path, buf)?, + TypeRef::Path(path) => print_path(db, path, buf)?, TypeRef::RawPtr(pointee, mtbl) => { let mtbl = match mtbl { Mutability::Shared => "*const", Mutability::Mut => "*mut", }; write!(buf, "{mtbl} ")?; - print_type_ref(pointee, buf)?; + print_type_ref(db, pointee, buf)?; } TypeRef::Reference(pointee, lt, mtbl) => { let mtbl = match mtbl { @@ -128,19 +143,19 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re }; write!(buf, "&")?; if let Some(lt) = lt { - write!(buf, "{} ", lt.name)?; + write!(buf, "{} ", lt.name.display(db))?; } write!(buf, "{mtbl}")?; - print_type_ref(pointee, buf)?; + print_type_ref(db, pointee, buf)?; } TypeRef::Array(elem, len) => { write!(buf, "[")?; - print_type_ref(elem, buf)?; - write!(buf, "; {len}]")?; + print_type_ref(db, elem, buf)?; + write!(buf, "; {}]", len.display(db))?; } TypeRef::Slice(elem) => { write!(buf, "[")?; - print_type_ref(elem, buf)?; + print_type_ref(db, elem, buf)?; write!(buf, "]")?; } TypeRef::Fn(args_and_ret, varargs, is_unsafe) => { @@ -154,7 +169,7 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re if i != 0 { write!(buf, ", ")?; } - print_type_ref(typeref, buf)?; + print_type_ref(db, typeref, buf)?; } if *varargs { if !args.is_empty() { @@ -163,7 +178,7 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re write!(buf, "...")?; } write!(buf, ") -> ")?; - print_type_ref(return_type, buf)?; + print_type_ref(db, return_type, buf)?; } TypeRef::Macro(_ast_id) => { write!(buf, "")?; @@ -171,11 +186,11 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re TypeRef::Error => write!(buf, "{{unknown}}")?, TypeRef::ImplTrait(bounds) => { write!(buf, "impl ")?; - print_type_bounds(bounds, buf)?; + print_type_bounds(db, bounds, buf)?; } TypeRef::DynTrait(bounds) => { write!(buf, "dyn ")?; - print_type_bounds(bounds, buf)?; + print_type_bounds(db, bounds, buf)?; } } @@ -183,6 +198,7 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re } pub(crate) fn print_type_bounds( + db: &dyn ExpandDatabase, bounds: &[Interned], buf: &mut dyn Write, ) -> fmt::Result { @@ -197,13 +213,13 @@ pub(crate) fn print_type_bounds( TraitBoundModifier::None => (), TraitBoundModifier::Maybe => write!(buf, "?")?, } - print_path(path, buf)?; + print_path(db, path, buf)?; } TypeBound::ForLifetime(lifetimes, path) => { - write!(buf, "for<{}> ", lifetimes.iter().format(", "))?; - print_path(path, buf)?; + write!(buf, "for<{}> ", lifetimes.iter().map(|it| it.display(db)).format(", "))?; + print_path(db, path, buf)?; } - TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name)?, + TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db))?, TypeBound::Error => write!(buf, "{{unknown}}")?, } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs index 61e64fc10363..06f5b2526a45 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -1,5 +1,5 @@ //! Name resolution façade. -use std::{fmt, hash::BuildHasherDefault, sync::Arc}; +use std::{fmt, hash::BuildHasherDefault}; use base_db::CrateId; use hir_expand::name::{name, Name}; @@ -7,16 +7,18 @@ use intern::Interned; use rustc_hash::FxHashSet; use smallvec::{smallvec, SmallVec}; +use triomphe::Arc; use crate::{ body::scope::{ExprScopes, ScopeId}, builtin_type::BuiltinType, db::DefDatabase, - expr::{BindingId, ExprId, LabelId}, generics::{GenericParams, TypeOrConstParamData}, + hir::{BindingId, ExprId, LabelId}, item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, - nameres::DefMap, - path::{ModPath, PathKind}, + lang_item::LangItemTarget, + nameres::{DefMap, MacroSubNs}, + path::{ModPath, Path, PathKind}, per_ns::PerNs, visibility::{RawVisibility, Visibility}, AdtId, AssocItemId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, @@ -78,7 +80,7 @@ enum Scope { ExprScope(ExprScope), } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum TypeNs { SelfType(ImplId), GenericParam(TypeParamId), @@ -153,7 +155,8 @@ pub fn resolve_module_path_in_trait_assoc_items( path: &ModPath, ) -> Option { let (item_map, module) = self.item_scope(); - let (module_res, idx) = item_map.resolve_path(db, module, path, BuiltinShadowMode::Module); + let (module_res, idx) = + item_map.resolve_path(db, module, path, BuiltinShadowMode::Module, None); match module_res.take_types()? { ModuleDefId::TraitId(it) => { let idx = idx?; @@ -176,8 +179,27 @@ pub fn resolve_module_path_in_trait_assoc_items( pub fn resolve_path_in_type_ns( &self, db: &dyn DefDatabase, - path: &ModPath, + path: &Path, ) -> Option<(TypeNs, Option)> { + let path = match path { + Path::Normal { mod_path, .. } => mod_path, + Path::LangItem(l) => { + return Some(( + match *l { + LangItemTarget::Union(x) => TypeNs::AdtId(x.into()), + LangItemTarget::TypeAlias(x) => TypeNs::TypeAliasId(x), + LangItemTarget::Struct(x) => TypeNs::AdtId(x.into()), + LangItemTarget::EnumVariant(x) => TypeNs::EnumVariantId(x), + LangItemTarget::EnumId(x) => TypeNs::AdtId(x.into()), + LangItemTarget::Trait(x) => TypeNs::TraitId(x), + LangItemTarget::Function(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::Static(_) => return None, + }, + None, + )) + } + }; let first_name = path.segments().first()?; let skip_to_mod = path.kind != PathKind::Plain; if skip_to_mod { @@ -217,7 +239,7 @@ pub fn resolve_path_in_type_ns( pub fn resolve_path_in_type_ns_fully( &self, db: &dyn DefDatabase, - path: &ModPath, + path: &Path, ) -> Option { let (res, unresolved) = self.resolve_path_in_type_ns(db, path)?; if unresolved.is_some() { @@ -245,8 +267,24 @@ pub fn resolve_visibility( pub fn resolve_path_in_value_ns( &self, db: &dyn DefDatabase, - path: &ModPath, + path: &Path, ) -> Option { + let path = match path { + Path::Normal { mod_path, .. } => mod_path, + Path::LangItem(l) => { + return Some(ResolveValueResult::ValueNs(match *l { + LangItemTarget::Function(x) => ValueNs::FunctionId(x), + LangItemTarget::Static(x) => ValueNs::StaticId(x), + LangItemTarget::Struct(x) => ValueNs::StructId(x), + LangItemTarget::EnumVariant(x) => ValueNs::EnumVariantId(x), + LangItemTarget::Union(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::TypeAlias(_) + | LangItemTarget::Trait(_) + | LangItemTarget::EnumId(_) => return None, + })) + } + }; let n_segments = path.segments().len(); let tmp = name![self]; let first_name = if path.is_self() { &tmp } else { path.segments().first()? }; @@ -340,7 +378,7 @@ pub fn resolve_path_in_value_ns( pub fn resolve_path_in_value_ns_fully( &self, db: &dyn DefDatabase, - path: &ModPath, + path: &Path, ) -> Option { match self.resolve_path_in_value_ns(db, path)? { ResolveValueResult::ValueNs(it) => Some(it), @@ -348,9 +386,17 @@ pub fn resolve_path_in_value_ns_fully( } } - pub fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option { + pub fn resolve_path_as_macro( + &self, + db: &dyn DefDatabase, + path: &ModPath, + expected_macro_kind: Option, + ) -> Option { let (item_map, module) = self.item_scope(); - item_map.resolve_path(db, module, path, BuiltinShadowMode::Other).0.take_macros() + item_map + .resolve_path(db, module, path, BuiltinShadowMode::Other, expected_macro_kind) + .0 + .take_macros() } /// Returns a set of names available in the current scope. @@ -415,7 +461,10 @@ pub fn names_in_scope( res.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac))); }) }); - def_map.extern_prelude().for_each(|(name, &def)| { + def_map.macro_use_prelude().for_each(|(name, def)| { + res.add(name, ScopeDef::ModuleDef(def.into())); + }); + def_map.extern_prelude().for_each(|(name, def)| { res.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def))); }); BUILTIN_SCOPE.iter().for_each(|(name, &def)| { @@ -441,7 +490,7 @@ pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet { &Scope::ImplDefScope(impl_) => { if let Some(target_trait) = &db.impl_data(impl_).target_trait { if let Some(TypeNs::TraitId(trait_)) = - self.resolve_path_in_type_ns_fully(db, target_trait.path.mod_path()) + self.resolve_path_in_type_ns_fully(db, &target_trait.path) { traits.insert(trait_); } @@ -536,15 +585,13 @@ fn append_expr_scope( scope_id, })); if let Some(block) = expr_scopes.block(scope_id) { - if let Some(def_map) = db.block_def_map(block) { - let root = def_map.root(); - resolver - .scopes - .push(Scope::BlockScope(ModuleItemMap { def_map, module_id: root })); - // FIXME: This adds as many module scopes as there are blocks, but resolving in each - // already traverses all parents, so this is O(n²). I think we could only store the - // innermost module scope instead? - } + let def_map = db.block_def_map(block); + resolver + .scopes + .push(Scope::BlockScope(ModuleItemMap { def_map, module_id: DefMap::ROOT })); + // FIXME: This adds as many module scopes as there are blocks, but resolving in each + // already traverses all parents, so this is O(n²). I think we could only store the + // innermost module scope instead? } } @@ -592,7 +639,8 @@ fn resolve_module_path( shadow: BuiltinShadowMode, ) -> PerNs { let (item_map, module) = self.item_scope(); - let (module_res, segment_index) = item_map.resolve_path(db, module, path, shadow); + // This method resolves `path` just like import paths, so no expected macro subns is given. + let (module_res, segment_index) = item_map.resolve_path(db, module, path, shadow, None); if segment_index.is_some() { return PerNs::none(); } @@ -705,13 +753,11 @@ fn resolver_for_scope_( for scope in scope_chain.into_iter().rev() { if let Some(block) = scopes.block(scope) { - if let Some(def_map) = db.block_def_map(block) { - let root = def_map.root(); - r = r.push_block_scope(def_map, root); - // FIXME: This adds as many module scopes as there are blocks, but resolving in each - // already traverses all parents, so this is O(n²). I think we could only store the - // innermost module scope instead? - } + let def_map = db.block_def_map(block); + r = r.push_block_scope(def_map, DefMap::ROOT); + // FIXME: This adds as many module scopes as there are blocks, but resolving in each + // already traverses all parents, so this is O(n²). I think we could only store the + // innermost module scope instead? } r = r.push_expr_scope(owner, Arc::clone(&scopes), scope); @@ -1000,6 +1046,12 @@ fn resolver(self, db: &dyn DefDatabase) -> Resolver { } } +impl HasResolver for EnumVariantId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.parent.resolver(db) + } +} + impl HasResolver for VariantId { fn resolver(self, db: &dyn DefDatabase) -> Resolver { match self { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/src.rs b/src/tools/rust-analyzer/crates/hir-def/src/src.rs index f69356cac87d..6047f770d4d3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/src.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/src.rs @@ -20,7 +20,7 @@ impl HasSource for AssocItemLoc { fn source(&self, db: &dyn DefDatabase) -> InFile { let tree = self.id.item_tree(db); let ast_id_map = db.ast_id_map(self.id.file_id()); - let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let root = db.parse_or_expand(self.id.file_id()); let node = &tree[self.id.value]; InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) @@ -33,7 +33,7 @@ impl HasSource for ItemLoc { fn source(&self, db: &dyn DefDatabase) -> InFile { let tree = self.id.item_tree(db); let ast_id_map = db.ast_id_map(self.id.file_id()); - let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let root = db.parse_or_expand(self.id.file_id()); let node = &tree[self.id.value]; InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) @@ -46,7 +46,7 @@ impl HasSource for Macro2Loc { fn source(&self, db: &dyn DefDatabase) -> InFile { let tree = self.id.item_tree(db); let ast_id_map = db.ast_id_map(self.id.file_id()); - let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let root = db.parse_or_expand(self.id.file_id()); let node = &tree[self.id.value]; InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) @@ -59,7 +59,7 @@ impl HasSource for MacroRulesLoc { fn source(&self, db: &dyn DefDatabase) -> InFile { let tree = self.id.item_tree(db); let ast_id_map = db.ast_id_map(self.id.file_id()); - let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let root = db.parse_or_expand(self.id.file_id()); let node = &tree[self.id.value]; InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) @@ -72,7 +72,7 @@ impl HasSource for ProcMacroLoc { fn source(&self, db: &dyn DefDatabase) -> InFile { let tree = self.id.item_tree(db); let ast_id_map = db.ast_id_map(self.id.file_id()); - let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let root = db.parse_or_expand(self.id.file_id()); let node = &tree[self.id.value]; InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs index ee143b19ae5b..a6befc8a81a8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -1,17 +1,16 @@ //! Database used for testing `hir_def`. -use std::{ - fmt, panic, - sync::{Arc, Mutex}, -}; +use std::{fmt, panic, sync::Mutex}; use base_db::{ - salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, - SourceDatabase, Upcast, + salsa::{self, Durability}, + AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, SourceDatabase, + Upcast, }; use hir_expand::{db::ExpandDatabase, InFile}; -use stdx::hash::NoHashHashSet; +use rustc_hash::FxHashSet; use syntax::{algo, ast, AstNode}; +use triomphe::Arc; use crate::{ db::DefDatabase, @@ -35,7 +34,7 @@ pub(crate) struct TestDB { impl Default for TestDB { fn default() -> Self { let mut this = Self { storage: Default::default(), events: Default::default() }; - this.set_enable_proc_attr_macros(true); + this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH); this } } @@ -70,13 +69,13 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { impl panic::RefUnwindSafe for TestDB {} impl FileLoader for TestDB { - fn file_text(&self, file_id: FileId) -> Arc { + fn file_text(&self, file_id: FileId) -> Arc { FileLoaderDelegate(self).file_text(file_id) } fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { FileLoaderDelegate(self).resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } } @@ -111,7 +110,7 @@ pub(crate) fn module_at_position(&self, position: FilePosition) -> ModuleId { } _ => { // FIXME: handle `mod` inside block expression - return def_map.module_id(def_map.root()); + return def_map.module_id(DefMap::ROOT); } } } @@ -120,7 +119,7 @@ pub(crate) fn module_at_position(&self, position: FilePosition) -> ModuleId { /// Finds the smallest/innermost module in `def_map` containing `position`. fn mod_at_position(&self, def_map: &DefMap, position: FilePosition) -> LocalModuleId { let mut size = None; - let mut res = def_map.root(); + let mut res = DefMap::ROOT; for (module, data) in def_map.modules() { let src = data.definition_source(self); if src.file_id != position.file_id.into() { @@ -209,13 +208,11 @@ fn block_at_position(&self, def_map: &DefMap, position: FilePosition) -> Option< }); for scope in scope_iter { - let containing_blocks = + let mut containing_blocks = scopes.scope_chain(Some(scope)).filter_map(|scope| scopes.block(scope)); - for block in containing_blocks { - if let Some(def_map) = self.block_def_map(block) { - return Some(def_map); - } + if let Some(block) = containing_blocks.next().map(|block| self.block_def_map(block)) { + return Some(block); } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs index ab76ed43d3a0..30f48de61f2e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs @@ -1,10 +1,11 @@ //! Defines hir-level representation of visibility (e.g. `pub` and `pub(crate)`). -use std::{iter, sync::Arc}; +use std::iter; use hir_expand::{hygiene::Hygiene, InFile}; use la_arena::ArenaMap; use syntax::ast; +use triomphe::Arc; use crate::{ db::DefDatabase, diff --git a/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml b/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml index 5c684be03cf2..40d8659f25ba 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml @@ -22,6 +22,7 @@ hashbrown = { version = "0.12.1", features = [ "inline-more", ], default-features = false } smallvec.workspace = true +triomphe.workspace = true # local deps stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs b/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs index 2b27db0e9506..400442de94b9 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs @@ -115,6 +115,7 @@ pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap { } } } + res.arena.shrink_to_fit(); res } @@ -123,6 +124,10 @@ pub fn ast_id(&self, item: &N) -> FileAstId { FileAstId { raw, _ty: PhantomData } } + pub fn get(&self, id: FileAstId) -> AstPtr { + AstPtr::try_from_raw(self.arena[id.raw].clone()).unwrap() + } + fn erased_ast_id(&self, item: &SyntaxNode) -> ErasedFileAstId { let ptr = SyntaxNodePtr::new(item); let hash = hash_ptr(&ptr); @@ -136,10 +141,6 @@ fn erased_ast_id(&self, item: &SyntaxNode) -> ErasedFileAstId { } } - pub fn get(&self, id: FileAstId) -> AstPtr { - AstPtr::try_from_raw(self.arena[id.raw].clone()).unwrap() - } - fn alloc(&mut self, item: &SyntaxNode) -> ErasedFileAstId { self.arena.alloc(SyntaxNodePtr::new(item)) } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs index 8d1e88725ecb..0c369a18bb9c 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -1,5 +1,5 @@ //! A higher level attributes based on TokenTree, with also some shortcuts. -use std::{fmt, ops, sync::Arc}; +use std::{fmt, ops}; use base_db::CrateId; use cfg::CfgExpr; @@ -8,12 +8,12 @@ use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; use smallvec::{smallvec, SmallVec}; use syntax::{ast, match_ast, AstNode, SmolStr, SyntaxNode}; +use triomphe::Arc; use crate::{ db::ExpandDatabase, hygiene::Hygiene, - mod_path::{ModPath, PathKind}, - name::AsName, + mod_path::ModPath, tt::{self, Subtree}, InFile, }; @@ -21,6 +21,7 @@ /// Syntactical attributes, without filtering of `cfg_attr`s. #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct RawAttrs { + // FIXME: Make this a ThinArc entries: Option>, } @@ -50,7 +51,9 @@ pub fn new(db: &dyn ExpandDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene path: Interned::new(ModPath::from(crate::name!(doc))), }), }) - .collect::>(); + .collect::>(); + // FIXME: use `Arc::from_iter` when it becomes available + let entries: Arc<[Attr]> = Arc::from(entries); Self { entries: if entries.is_empty() { None } else { Some(entries) } } } @@ -68,7 +71,7 @@ pub fn merge(&self, other: Self) -> Self { (Some(a), Some(b)) => { let last_ast_index = a.last().map_or(0, |it| it.id.ast_index() + 1) as u32; Self { - entries: Some( + entries: Some(Arc::from( a.iter() .cloned() .chain(b.iter().map(|it| { @@ -78,8 +81,9 @@ pub fn merge(&self, other: Self) -> Self { << AttrId::AST_INDEX_BITS; it })) - .collect(), - ), + // FIXME: use `Arc::from_iter` when it becomes available + .collect::>(), + )), } } } @@ -96,48 +100,51 @@ pub fn filter(self, db: &dyn ExpandDatabase, krate: CrateId) -> RawAttrs { } let crate_graph = db.crate_graph(); - let new_attrs = self - .iter() - .flat_map(|attr| -> SmallVec<[_; 1]> { - let is_cfg_attr = - attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr]); - if !is_cfg_attr { - return smallvec![attr.clone()]; - } + let new_attrs = Arc::from( + self.iter() + .flat_map(|attr| -> SmallVec<[_; 1]> { + let is_cfg_attr = + attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr]); + if !is_cfg_attr { + return smallvec![attr.clone()]; + } - let subtree = match attr.token_tree_value() { - Some(it) => it, - _ => return smallvec![attr.clone()], - }; + let subtree = match attr.token_tree_value() { + Some(it) => it, + _ => return smallvec![attr.clone()], + }; - let (cfg, parts) = match parse_cfg_attr_input(subtree) { - Some(it) => it, - None => return smallvec![attr.clone()], - }; - let index = attr.id; - let attrs = - parts.enumerate().take(1 << AttrId::CFG_ATTR_BITS).filter_map(|(idx, attr)| { - let tree = Subtree { - delimiter: tt::Delimiter::unspecified(), - token_trees: attr.to_vec(), - }; - // FIXME hygiene - let hygiene = Hygiene::new_unhygienic(); - Attr::from_tt(db, &tree, &hygiene, index.with_cfg_attr(idx)) - }); + let (cfg, parts) = match parse_cfg_attr_input(subtree) { + Some(it) => it, + None => return smallvec![attr.clone()], + }; + let index = attr.id; + let attrs = parts.enumerate().take(1 << AttrId::CFG_ATTR_BITS).filter_map( + |(idx, attr)| { + let tree = Subtree { + delimiter: tt::Delimiter::unspecified(), + token_trees: attr.to_vec(), + }; + // FIXME hygiene + let hygiene = Hygiene::new_unhygienic(); + Attr::from_tt(db, &tree, &hygiene, index.with_cfg_attr(idx)) + }, + ); - let cfg_options = &crate_graph[krate].cfg_options; - let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; - let cfg = CfgExpr::parse(&cfg); - if cfg_options.check(&cfg) == Some(false) { - smallvec![] - } else { - cov_mark::hit!(cfg_attr_active); + let cfg_options = &crate_graph[krate].cfg_options; + let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; + let cfg = CfgExpr::parse(&cfg); + if cfg_options.check(&cfg) == Some(false) { + smallvec![] + } else { + cov_mark::hit!(cfg_attr_active); - attrs.collect() - } - }) - .collect(); + attrs.collect() + } + }) + // FIXME: use `Arc::from_iter` when it becomes available + .collect::>(), + ); RawAttrs { entries: Some(new_attrs) } } @@ -266,7 +273,11 @@ pub fn token_tree_value(&self) -> Option<&Subtree> { } /// Parses this attribute as a token tree consisting of comma separated paths. - pub fn parse_path_comma_token_tree(&self) -> Option + '_> { + pub fn parse_path_comma_token_tree<'a>( + &'a self, + db: &'a dyn ExpandDatabase, + hygiene: &'a Hygiene, + ) -> Option + 'a> { let args = self.token_tree_value()?; if args.delimiter.kind != DelimiterKind::Parenthesis { @@ -275,19 +286,37 @@ pub fn parse_path_comma_token_tree(&self) -> Option Some(id.as_name()), - _ => None, - }); - Some(ModPath::from_segments(PathKind::Plain, segments)) + // FIXME: This is necessarily a hack. It'd be nice if we could avoid allocation here. + let subtree = tt::Subtree { + delimiter: tt::Delimiter::unspecified(), + token_trees: tts.into_iter().cloned().collect(), + }; + let (parse, _) = + mbe::token_tree_to_syntax_node(&subtree, mbe::TopEntryPoint::MetaItem); + let meta = ast::Meta::cast(parse.syntax_node())?; + // Only simple paths are allowed. + if meta.eq_token().is_some() || meta.expr().is_some() || meta.token_tree().is_some() + { + return None; + } + let path = meta.path()?; + ModPath::from_src(db, path, hygiene) }); Some(paths) } + + pub fn cfg(&self) -> Option { + if *self.path.as_ident()? == crate::name![cfg] { + self.token_tree_value().map(CfgExpr::parse) + } else { + None + } + } } pub fn collect_attrs( diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs index 277ecd939942..80695bc06562 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs @@ -96,7 +96,7 @@ fn derive_attr_expand( ) -> ExpandResult { let loc = db.lookup_intern_macro_call(id); let derives = match &loc.kind { - MacroCallKind::Attr { attr_args, is_derive: true, .. } => &attr_args.0, + MacroCallKind::Attr { attr_args, .. } if loc.def.is_attribute_derive() => &attr_args.0, _ => return ExpandResult::ok(tt::Subtree::empty()), }; pseudo_derive_attr_expansion(tt, derives) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs index 5c1a75132ee9..54706943ac4f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs @@ -1,11 +1,19 @@ //! Builtin derives. +use ::tt::Ident; use base_db::{CrateOrigin, LangCrateOrigin}; +use itertools::izip; +use mbe::TokenMap; +use std::collections::HashSet; +use stdx::never; use tracing::debug; use crate::tt::{self, TokenId}; use syntax::{ - ast::{self, AstNode, HasGenericParams, HasModuleItem, HasName}, + ast::{ + self, AstNode, FieldList, HasAttrs, HasGenericParams, HasModuleItem, HasName, + HasTypeBounds, PathType, + }, match_ast, }; @@ -58,10 +66,129 @@ pub fn find_builtin_derive(ident: &name::Name) -> Option BuiltinDeriveExpander::find_by_name(ident) } +enum VariantShape { + Struct(Vec), + Tuple(usize), + Unit, +} + +fn tuple_field_iterator(n: usize) -> impl Iterator { + (0..n).map(|x| Ident::new(format!("f{x}"), tt::TokenId::unspecified())) +} + +impl VariantShape { + fn as_pattern(&self, path: tt::Subtree) -> tt::Subtree { + self.as_pattern_map(path, |x| quote!(#x)) + } + + fn field_names(&self) -> Vec { + match self { + VariantShape::Struct(s) => s.clone(), + VariantShape::Tuple(n) => tuple_field_iterator(*n).collect(), + VariantShape::Unit => vec![], + } + } + + fn as_pattern_map( + &self, + path: tt::Subtree, + field_map: impl Fn(&tt::Ident) -> tt::Subtree, + ) -> tt::Subtree { + match self { + VariantShape::Struct(fields) => { + let fields = fields.iter().map(|x| { + let mapped = field_map(x); + quote! { #x : #mapped , } + }); + quote! { + #path { ##fields } + } + } + &VariantShape::Tuple(n) => { + let fields = tuple_field_iterator(n).map(|x| { + let mapped = field_map(&x); + quote! { + #mapped , + } + }); + quote! { + #path ( ##fields ) + } + } + VariantShape::Unit => path, + } + } + + fn from(value: Option, token_map: &TokenMap) -> Result { + let r = match value { + None => VariantShape::Unit, + Some(FieldList::RecordFieldList(x)) => VariantShape::Struct( + x.fields() + .map(|x| x.name()) + .map(|x| name_to_token(token_map, x)) + .collect::>()?, + ), + Some(FieldList::TupleFieldList(x)) => VariantShape::Tuple(x.fields().count()), + }; + Ok(r) + } +} + +enum AdtShape { + Struct(VariantShape), + Enum { variants: Vec<(tt::Ident, VariantShape)>, default_variant: Option }, + Union, +} + +impl AdtShape { + fn as_pattern(&self, name: &tt::Ident) -> Vec { + self.as_pattern_map(name, |x| quote!(#x)) + } + + fn field_names(&self) -> Vec> { + match self { + AdtShape::Struct(s) => { + vec![s.field_names()] + } + AdtShape::Enum { variants, .. } => { + variants.iter().map(|(_, fields)| fields.field_names()).collect() + } + AdtShape::Union => { + never!("using fields of union in derive is always wrong"); + vec![] + } + } + } + + fn as_pattern_map( + &self, + name: &tt::Ident, + field_map: impl Fn(&tt::Ident) -> tt::Subtree, + ) -> Vec { + match self { + AdtShape::Struct(s) => { + vec![s.as_pattern_map(quote! { #name }, field_map)] + } + AdtShape::Enum { variants, .. } => variants + .iter() + .map(|(v, fields)| fields.as_pattern_map(quote! { #name :: #v }, &field_map)) + .collect(), + AdtShape::Union => { + never!("pattern matching on union is always wrong"); + vec![quote! { un }] + } + } + } +} + struct BasicAdtInfo { name: tt::Ident, - /// `Some(ty)` if it's a const param of type `ty`, `None` if it's a type param. - param_types: Vec>, + shape: AdtShape, + /// first field is the name, and + /// second field is `Some(ty)` if it's a const param of type `ty`, `None` if it's a type param. + /// third fields is where bounds, if any + param_types: Vec<(tt::Subtree, Option, Option)>, + associated_types: Vec, } fn parse_adt(tt: &tt::Subtree) -> Result { @@ -75,29 +202,52 @@ fn parse_adt(tt: &tt::Subtree) -> Result { ExpandError::Other("no item found".into()) })?; let node = item.syntax(); - let (name, params) = match_ast! { + let (name, params, shape) = match_ast! { match node { - ast::Struct(it) => (it.name(), it.generic_param_list()), - ast::Enum(it) => (it.name(), it.generic_param_list()), - ast::Union(it) => (it.name(), it.generic_param_list()), + ast::Struct(it) => (it.name(), it.generic_param_list(), AdtShape::Struct(VariantShape::from(it.field_list(), &token_map)?)), + ast::Enum(it) => { + let default_variant = it.variant_list().into_iter().flat_map(|x| x.variants()).position(|x| x.attrs().any(|x| x.simple_name() == Some("default".into()))); + ( + it.name(), + it.generic_param_list(), + AdtShape::Enum { + default_variant, + variants: it.variant_list() + .into_iter() + .flat_map(|x| x.variants()) + .map(|x| Ok((name_to_token(&token_map,x.name())?, VariantShape::from(x.field_list(), &token_map)?))).collect::>()? + } + ) + }, + ast::Union(it) => (it.name(), it.generic_param_list(), AdtShape::Union), _ => { debug!("unexpected node is {:?}", node); return Err(ExpandError::Other("expected struct, enum or union".into())) }, } }; - let name = name.ok_or_else(|| { - debug!("parsed item has no name"); - ExpandError::Other("missing name".into()) - })?; - let name_token_id = - token_map.token_by_range(name.syntax().text_range()).unwrap_or_else(TokenId::unspecified); - let name_token = tt::Ident { span: name_token_id, text: name.text().into() }; + let mut param_type_set: HashSet = HashSet::new(); let param_types = params .into_iter() .flat_map(|param_list| param_list.type_or_const_params()) .map(|param| { - if let ast::TypeOrConstParam::Const(param) = param { + let name = { + let this = param.name(); + match this { + Some(x) => { + param_type_set.insert(x.to_string()); + mbe::syntax_node_to_token_tree(x.syntax()).0 + } + None => tt::Subtree::empty(), + } + }; + let bounds = match ¶m { + ast::TypeOrConstParam::Type(x) => { + x.type_bound_list().map(|x| mbe::syntax_node_to_token_tree(x.syntax()).0) + } + ast::TypeOrConstParam::Const(_) => None, + }; + let ty = if let ast::TypeOrConstParam::Const(param) = param { let ty = param .ty() .map(|ty| mbe::syntax_node_to_token_tree(ty.syntax()).0) @@ -105,27 +255,107 @@ fn parse_adt(tt: &tt::Subtree) -> Result { Some(ty) } else { None - } + }; + (name, ty, bounds) }) .collect(); - Ok(BasicAdtInfo { name: name_token, param_types }) + let is_associated_type = |p: &PathType| { + if let Some(p) = p.path() { + if let Some(parent) = p.qualifier() { + if let Some(x) = parent.segment() { + if let Some(x) = x.path_type() { + if let Some(x) = x.path() { + if let Some(pname) = x.as_single_name_ref() { + if param_type_set.contains(&pname.to_string()) { + // ::Assoc + return true; + } + } + } + } + } + if let Some(pname) = parent.as_single_name_ref() { + if param_type_set.contains(&pname.to_string()) { + // T::Assoc + return true; + } + } + } + } + false + }; + let associated_types = node + .descendants() + .filter_map(PathType::cast) + .filter(is_associated_type) + .map(|x| mbe::syntax_node_to_token_tree(x.syntax()).0) + .collect::>(); + let name_token = name_to_token(&token_map, name)?; + Ok(BasicAdtInfo { name: name_token, shape, param_types, associated_types }) } -fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResult { +fn name_to_token(token_map: &TokenMap, name: Option) -> Result { + let name = name.ok_or_else(|| { + debug!("parsed item has no name"); + ExpandError::Other("missing name".into()) + })?; + let name_token_id = + token_map.token_by_range(name.syntax().text_range()).unwrap_or_else(TokenId::unspecified); + let name_token = tt::Ident { span: name_token_id, text: name.text().into() }; + Ok(name_token) +} + +/// Given that we are deriving a trait `DerivedTrait` for a type like: +/// +/// ```ignore (only-for-syntax-highlight) +/// struct Struct<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z> where C: WhereTrait { +/// a: A, +/// b: B::Item, +/// b1: ::Item, +/// c1: ::Item, +/// c2: Option<::Item>, +/// ... +/// } +/// ``` +/// +/// create an impl like: +/// +/// ```ignore (only-for-syntax-highlight) +/// impl<'a, ..., 'z, A, B: DeclaredTrait, C, ... Z> where +/// C: WhereTrait, +/// A: DerivedTrait + B1 + ... + BN, +/// B: DerivedTrait + B1 + ... + BN, +/// C: DerivedTrait + B1 + ... + BN, +/// B::Item: DerivedTrait + B1 + ... + BN, +/// ::Item: DerivedTrait + B1 + ... + BN, +/// ... +/// { +/// ... +/// } +/// ``` +/// +/// where B1, ..., BN are the bounds given by `bounds_paths`.'. Z is a phantom type, and +/// therefore does not get bound by the derived trait. +fn expand_simple_derive( + tt: &tt::Subtree, + trait_path: tt::Subtree, + trait_body: impl FnOnce(&BasicAdtInfo) -> tt::Subtree, +) -> ExpandResult { let info = match parse_adt(tt) { Ok(info) => info, - Err(e) => return ExpandResult::with_err(tt::Subtree::empty(), e), + Err(e) => return ExpandResult::new(tt::Subtree::empty(), e), }; + let trait_body = trait_body(&info); + let mut where_block = vec![]; let (params, args): (Vec<_>, Vec<_>) = info .param_types .into_iter() - .enumerate() - .map(|(idx, param_ty)| { - let ident = tt::Leaf::Ident(tt::Ident { - span: tt::TokenId::unspecified(), - text: format!("T{idx}").into(), - }); + .map(|(ident, param_ty, bound)| { let ident_ = ident.clone(); + if let Some(b) = bound { + let ident = ident.clone(); + where_block.push(quote! { #ident : #b , }); + } if let Some(ty) = param_ty { (quote! { const #ident : #ty , }, quote! { #ident_ , }) } else { @@ -134,9 +364,16 @@ fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResu } }) .unzip(); + + where_block.extend(info.associated_types.iter().map(|x| { + let x = x.clone(); + let bound = trait_path.clone(); + quote! { #x : #bound , } + })); + let name = info.name; let expanded = quote! { - impl < ##params > #trait_path for #name < ##args > {} + impl < ##params > #trait_path for #name < ##args > where ##where_block { #trait_body } }; ExpandResult::ok(expanded) } @@ -163,7 +400,7 @@ fn copy_expand( tt: &tt::Subtree, ) -> ExpandResult { let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::marker::Copy }) + expand_simple_derive(tt, quote! { #krate::marker::Copy }, |_| quote! {}) } fn clone_expand( @@ -172,7 +409,63 @@ fn clone_expand( tt: &tt::Subtree, ) -> ExpandResult { let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::clone::Clone }) + expand_simple_derive(tt, quote! { #krate::clone::Clone }, |adt| { + if matches!(adt.shape, AdtShape::Union) { + let star = tt::Punct { + char: '*', + spacing: ::tt::Spacing::Alone, + span: tt::TokenId::unspecified(), + }; + return quote! { + fn clone(&self) -> Self { + #star self + } + }; + } + if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { + let star = tt::Punct { + char: '*', + spacing: ::tt::Spacing::Alone, + span: tt::TokenId::unspecified(), + }; + return quote! { + fn clone(&self) -> Self { + match #star self {} + } + }; + } + let name = &adt.name; + let patterns = adt.shape.as_pattern(name); + let exprs = adt.shape.as_pattern_map(name, |x| quote! { #x .clone() }); + let arms = patterns.into_iter().zip(exprs.into_iter()).map(|(pat, expr)| { + let fat_arrow = fat_arrow(); + quote! { + #pat #fat_arrow #expr, + } + }); + + quote! { + fn clone(&self) -> Self { + match self { + ##arms + } + } + } + }) +} + +/// This function exists since `quote! { => }` doesn't work. +fn fat_arrow() -> ::tt::Subtree { + let eq = + tt::Punct { char: '=', spacing: ::tt::Spacing::Joint, span: tt::TokenId::unspecified() }; + quote! { #eq> } +} + +/// This function exists since `quote! { && }` doesn't work. +fn and_and() -> ::tt::Subtree { + let and = + tt::Punct { char: '&', spacing: ::tt::Spacing::Joint, span: tt::TokenId::unspecified() }; + quote! { #and& } } fn default_expand( @@ -180,8 +473,38 @@ fn default_expand( id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::default::Default }) + let krate = &find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::default::Default }, |adt| { + let body = match &adt.shape { + AdtShape::Struct(fields) => { + let name = &adt.name; + fields + .as_pattern_map(quote!(#name), |_| quote!(#krate::default::Default::default())) + } + AdtShape::Enum { default_variant, variants } => { + if let Some(d) = default_variant { + let (name, fields) = &variants[*d]; + let adt_name = &adt.name; + fields.as_pattern_map( + quote!(#adt_name :: #name), + |_| quote!(#krate::default::Default::default()), + ) + } else { + // FIXME: Return expand error here + quote!() + } + } + AdtShape::Union => { + // FIXME: Return expand error here + quote!() + } + }; + quote! { + fn default() -> Self { + #body + } + } + }) } fn debug_expand( @@ -189,8 +512,79 @@ fn debug_expand( id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::fmt::Debug }) + let krate = &find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::fmt::Debug }, |adt| { + let for_variant = |name: String, v: &VariantShape| match v { + VariantShape::Struct(fields) => { + let for_fields = fields.iter().map(|x| { + let x_string = x.to_string(); + quote! { + .field(#x_string, & #x) + } + }); + quote! { + f.debug_struct(#name) ##for_fields .finish() + } + } + VariantShape::Tuple(n) => { + let for_fields = tuple_field_iterator(*n).map(|x| { + quote! { + .field( & #x) + } + }); + quote! { + f.debug_tuple(#name) ##for_fields .finish() + } + } + VariantShape::Unit => quote! { + f.write_str(#name) + }, + }; + if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { + let star = tt::Punct { + char: '*', + spacing: ::tt::Spacing::Alone, + span: tt::TokenId::unspecified(), + }; + return quote! { + fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { + match #star self {} + } + }; + } + let arms = match &adt.shape { + AdtShape::Struct(fields) => { + let fat_arrow = fat_arrow(); + let name = &adt.name; + let pat = fields.as_pattern(quote!(#name)); + let expr = for_variant(name.to_string(), fields); + vec![quote! { #pat #fat_arrow #expr }] + } + AdtShape::Enum { variants, .. } => variants + .iter() + .map(|(name, v)| { + let fat_arrow = fat_arrow(); + let adt_name = &adt.name; + let pat = v.as_pattern(quote!(#adt_name :: #name)); + let expr = for_variant(name.to_string(), v); + quote! { + #pat #fat_arrow #expr , + } + }) + .collect(), + AdtShape::Union => { + // FIXME: Return expand error here + vec![] + } + }; + quote! { + fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { + match self { + ##arms + } + } + } + }) } fn hash_expand( @@ -198,8 +592,47 @@ fn hash_expand( id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::hash::Hash }) + let krate = &find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::hash::Hash }, |adt| { + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote! {}; + } + if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { + let star = tt::Punct { + char: '*', + spacing: ::tt::Spacing::Alone, + span: tt::TokenId::unspecified(), + }; + return quote! { + fn hash(&self, state: &mut H) { + match #star self {} + } + }; + } + let arms = adt.shape.as_pattern(&adt.name).into_iter().zip(adt.shape.field_names()).map( + |(pat, names)| { + let expr = { + let it = names.iter().map(|x| quote! { #x . hash(state); }); + quote! { { + ##it + } } + }; + let fat_arrow = fat_arrow(); + quote! { + #pat #fat_arrow #expr , + } + }, + ); + quote! { + fn hash(&self, state: &mut H) { + #krate::mem::discriminant(self).hash(state); + match self { + ##arms + } + } + } + }) } fn eq_expand( @@ -208,7 +641,7 @@ fn eq_expand( tt: &tt::Subtree, ) -> ExpandResult { let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::Eq }) + expand_simple_derive(tt, quote! { #krate::cmp::Eq }, |_| quote! {}) } fn partial_eq_expand( @@ -217,7 +650,65 @@ fn partial_eq_expand( tt: &tt::Subtree, ) -> ExpandResult { let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::PartialEq }) + expand_simple_derive(tt, quote! { #krate::cmp::PartialEq }, |adt| { + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote! {}; + } + let name = &adt.name; + + let (self_patterns, other_patterns) = self_and_other_patterns(adt, name); + let arms = izip!(self_patterns, other_patterns, adt.shape.field_names()).map( + |(pat1, pat2, names)| { + let fat_arrow = fat_arrow(); + let body = match &*names { + [] => { + quote!(true) + } + [first, rest @ ..] => { + let rest = rest.iter().map(|x| { + let t1 = Ident::new(format!("{}_self", x.text), x.span); + let t2 = Ident::new(format!("{}_other", x.text), x.span); + let and_and = and_and(); + quote!(#and_and #t1 .eq( #t2 )) + }); + let first = { + let t1 = Ident::new(format!("{}_self", first.text), first.span); + let t2 = Ident::new(format!("{}_other", first.text), first.span); + quote!(#t1 .eq( #t2 )) + }; + quote!(#first ##rest) + } + }; + quote! { ( #pat1 , #pat2 ) #fat_arrow #body , } + }, + ); + + let fat_arrow = fat_arrow(); + quote! { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ##arms + _unused #fat_arrow false + } + } + } + }) +} + +fn self_and_other_patterns( + adt: &BasicAdtInfo, + name: &tt::Ident, +) -> (Vec, Vec) { + let self_patterns = adt.shape.as_pattern_map(name, |x| { + let t = Ident::new(format!("{}_self", x.text), x.span); + quote!(#t) + }); + let other_patterns = adt.shape.as_pattern_map(name, |x| { + let t = Ident::new(format!("{}_other", x.text), x.span); + quote!(#t) + }); + (self_patterns, other_patterns) } fn ord_expand( @@ -225,8 +716,63 @@ fn ord_expand( id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::Ord }) + let krate = &find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::cmp::Ord }, |adt| { + fn compare( + krate: &tt::TokenTree, + left: tt::Subtree, + right: tt::Subtree, + rest: tt::Subtree, + ) -> tt::Subtree { + let fat_arrow1 = fat_arrow(); + let fat_arrow2 = fat_arrow(); + quote! { + match #left.cmp(&#right) { + #krate::cmp::Ordering::Equal #fat_arrow1 { + #rest + } + c #fat_arrow2 return c, + } + } + } + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote!(); + } + let left = quote!(#krate::intrinsics::discriminant_value(self)); + let right = quote!(#krate::intrinsics::discriminant_value(other)); + + let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name); + let arms = izip!(self_patterns, other_patterns, adt.shape.field_names()).map( + |(pat1, pat2, fields)| { + let mut body = quote!(#krate::cmp::Ordering::Equal); + for f in fields.into_iter().rev() { + let t1 = Ident::new(format!("{}_self", f.text), f.span); + let t2 = Ident::new(format!("{}_other", f.text), f.span); + body = compare(krate, quote!(#t1), quote!(#t2), body); + } + let fat_arrow = fat_arrow(); + quote! { ( #pat1 , #pat2 ) #fat_arrow #body , } + }, + ); + let fat_arrow = fat_arrow(); + let body = compare( + krate, + left, + right, + quote! { + match (self, other) { + ##arms + _unused #fat_arrow #krate::cmp::Ordering::Equal + } + }, + ); + quote! { + fn cmp(&self, other: &Self) -> #krate::cmp::Ordering { + #body + } + } + }) } fn partial_ord_expand( @@ -234,6 +780,61 @@ fn partial_ord_expand( id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::PartialOrd }) + let krate = &find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::cmp::PartialOrd }, |adt| { + fn compare( + krate: &tt::TokenTree, + left: tt::Subtree, + right: tt::Subtree, + rest: tt::Subtree, + ) -> tt::Subtree { + let fat_arrow1 = fat_arrow(); + let fat_arrow2 = fat_arrow(); + quote! { + match #left.partial_cmp(&#right) { + #krate::option::Option::Some(#krate::cmp::Ordering::Equal) #fat_arrow1 { + #rest + } + c #fat_arrow2 return c, + } + } + } + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote!(); + } + let left = quote!(#krate::intrinsics::discriminant_value(self)); + let right = quote!(#krate::intrinsics::discriminant_value(other)); + + let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name); + let arms = izip!(self_patterns, other_patterns, adt.shape.field_names()).map( + |(pat1, pat2, fields)| { + let mut body = quote!(#krate::option::Option::Some(#krate::cmp::Ordering::Equal)); + for f in fields.into_iter().rev() { + let t1 = Ident::new(format!("{}_self", f.text), f.span); + let t2 = Ident::new(format!("{}_other", f.text), f.span); + body = compare(krate, quote!(#t1), quote!(#t2), body); + } + let fat_arrow = fat_arrow(); + quote! { ( #pat1 , #pat2 ) #fat_arrow #body , } + }, + ); + let fat_arrow = fat_arrow(); + let body = compare( + krate, + left, + right, + quote! { + match (self, other) { + ##arms + _unused #fat_arrow #krate::option::Option::Some(#krate::cmp::Ordering::Equal) + } + }, + ); + quote! { + fn partial_cmp(&self, other: &Self) -> #krate::option::Option::Option<#krate::cmp::Ordering> { + #body + } + } + }) } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs index a9c5e1488aac..c7643bd0a18e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs @@ -1,9 +1,13 @@ //! Builtin macro +use std::mem; + +use ::tt::Ident; use base_db::{AnchoredPath, Edition, FileId}; use cfg::CfgExpr; use either::Either; -use mbe::{parse_exprs_with_sep, parse_to_token_tree}; +use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap}; +use rustc_hash::FxHashMap; use syntax::{ ast::{self, AstToken}, SmolStr, @@ -67,7 +71,7 @@ fn find_by_name(ident: &name::Name) -> Option, + pub(crate) included_file: Option<(FileId, TokenMap)>, } impl ExpandedEager { @@ -90,11 +94,6 @@ pub fn find_builtin_macro( (module_path, ModulePath) => module_path_expand, (assert, Assert) => assert_expand, (stringify, Stringify) => stringify_expand, - (format_args, FormatArgs) => format_args_expand, - (const_format_args, ConstFormatArgs) => format_args_expand, - // format_args_nl only differs in that it adds a newline in the end, - // so we use the same stub expansion for now - (format_args_nl, FormatArgsNl) => format_args_expand, (llvm_asm, LlvmAsm) => asm_expand, (asm, Asm) => asm_expand, (global_asm, GlobalAsm) => global_asm_expand, @@ -106,6 +105,9 @@ pub fn find_builtin_macro( (trace_macros, TraceMacros) => trace_macros_expand, EAGER: + (format_args, FormatArgs) => format_args_expand, + (const_format_args, ConstFormatArgs) => format_args_expand, + (format_args_nl, FormatArgsNl) => format_args_nl_expand, (compile_error, CompileError) => compile_error_expand, (concat, Concat) => concat_expand, (concat_idents, ConcatIdents) => concat_idents_expand, @@ -135,9 +137,8 @@ fn line_expand( _tt: &tt::Subtree, ) -> ExpandResult { // dummy implementation for type-checking purposes - let line_num = 0; let expanded = quote! { - #line_num + 0 as u32 }; ExpandResult::ok(expanded) @@ -179,9 +180,8 @@ fn column_expand( _tt: &tt::Subtree, ) -> ExpandResult { // dummy implementation for type-checking purposes - let col_num = 0; let expanded = quote! { - #col_num + 0 as u32 }; ExpandResult::ok(expanded) @@ -234,45 +234,173 @@ fn file_expand( } fn format_args_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + format_args_expand_general(db, id, tt, "") + .map(|x| ExpandedEager { subtree: x, included_file: None }) +} + +fn format_args_nl_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + format_args_expand_general(db, id, tt, "\\n") + .map(|x| ExpandedEager { subtree: x, included_file: None }) +} + +fn format_args_expand_general( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, + end_string: &str, ) -> ExpandResult { - // We expand `format_args!("", a1, a2)` to - // ``` - // $crate::fmt::Arguments::new_v1(&[], &[ - // $crate::fmt::Argument::new(&arg1,$crate::fmt::Display::fmt), - // $crate::fmt::Argument::new(&arg2,$crate::fmt::Display::fmt), - // ]) - // ```, - // which is still not really correct, but close enough for now - let mut args = parse_exprs_with_sep(tt, ','); + let args = parse_exprs_with_sep(tt, ','); + + let expand_error = + ExpandResult::new(tt::Subtree::empty(), mbe::ExpandError::NoMatchingRule.into()); if args.is_empty() { - return ExpandResult::with_err( - tt::Subtree::empty(), - mbe::ExpandError::NoMatchingRule.into(), - ); + return expand_error; } - for arg in &mut args { + let mut key_args = FxHashMap::default(); + let mut args = args.into_iter().filter_map(|mut arg| { // Remove `key =`. if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') { // but not with `==` - if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' ) + if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') { - arg.token_trees.drain(..2); + let key = arg.token_trees.drain(..2).next().unwrap(); + key_args.insert(key.to_string(), arg); + return None; } } - } - let _format_string = args.remove(0); - let arg_tts = args.into_iter().flat_map(|arg| { - quote! { #DOLLAR_CRATE::fmt::Argument::new(&(#arg), #DOLLAR_CRATE::fmt::Display::fmt), } - }.token_trees); - let expanded = quote! { - #DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts]) + Some(arg) + }).collect::>().into_iter(); + // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`) + let format_subtree = args.next().unwrap(); + let format_string = (|| { + let token_tree = format_subtree.token_trees.get(0)?; + match token_tree { + tt::TokenTree::Leaf(l) => match l { + tt::Leaf::Literal(l) => { + if let Some(mut text) = l.text.strip_prefix('r') { + let mut raw_sharps = String::new(); + while let Some(t) = text.strip_prefix('#') { + text = t; + raw_sharps.push('#'); + } + text = + text.strip_suffix(&raw_sharps)?.strip_prefix('"')?.strip_suffix('"')?; + Some((text, l.span, Some(raw_sharps))) + } else { + let text = l.text.strip_prefix('"')?.strip_suffix('"')?; + let span = l.span; + Some((text, span, None)) + } + } + _ => None, + }, + tt::TokenTree::Subtree(_) => None, + } + })(); + let Some((format_string, _format_string_span, raw_sharps)) = format_string else { + return expand_error; }; - ExpandResult::ok(expanded) + let mut format_iter = format_string.chars().peekable(); + let mut parts = vec![]; + let mut last_part = String::new(); + let mut arg_tts = vec![]; + let mut err = None; + while let Some(c) = format_iter.next() { + // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info + match c { + '{' => { + if format_iter.peek() == Some(&'{') { + format_iter.next(); + last_part.push('{'); + continue; + } + let mut argument = String::new(); + while ![Some(&'}'), Some(&':')].contains(&format_iter.peek()) { + argument.push(match format_iter.next() { + Some(c) => c, + None => return expand_error, + }); + } + let format_spec = match format_iter.next().unwrap() { + '}' => "".to_owned(), + ':' => { + let mut s = String::new(); + while let Some(c) = format_iter.next() { + if c == '}' { + break; + } + s.push(c); + } + s + } + _ => unreachable!(), + }; + parts.push(mem::take(&mut last_part)); + let arg_tree = if argument.is_empty() { + match args.next() { + Some(x) => x, + None => { + err = Some(mbe::ExpandError::NoMatchingRule.into()); + tt::Subtree::empty() + } + } + } else if let Some(tree) = key_args.get(&argument) { + tree.clone() + } else { + // FIXME: we should pick the related substring of the `_format_string_span` as the span. You + // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval. + let ident = Ident::new(argument, tt::TokenId::unspecified()); + quote!(#ident) + }; + let formatter = match &*format_spec { + "?" => quote!(::core::fmt::Debug::fmt), + "" => quote!(::core::fmt::Display::fmt), + _ => { + // FIXME: implement the rest and return expand error here + quote!(::core::fmt::Display::fmt) + } + }; + arg_tts.push(quote! { ::core::fmt::Argument::new(&(#arg_tree), #formatter), }); + } + '}' => { + if format_iter.peek() == Some(&'}') { + format_iter.next(); + last_part.push('}'); + } else { + return expand_error; + } + } + _ => last_part.push(c), + } + } + last_part += end_string; + if !last_part.is_empty() { + parts.push(last_part); + } + let part_tts = parts.into_iter().map(|x| { + let text = if let Some(raw) = &raw_sharps { + format!("r{raw}\"{}\"{raw}", x).into() + } else { + format!("\"{}\"", x).into() + }; + let l = tt::Literal { span: tt::TokenId::unspecified(), text }; + quote!(#l ,) + }); + let arg_tts = arg_tts.into_iter().flat_map(|arg| arg.token_trees); + let expanded = quote! { + ::core::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts]) + }; + ExpandResult { value: expanded, err } } fn asm_expand( @@ -566,16 +694,16 @@ fn include_expand( let path = parse_string(tt)?; let file_id = relative_file(db, arg_id, &path, false)?; - let subtree = - parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?.0; - Ok((subtree, file_id)) + let (subtree, map) = + parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?; + Ok((subtree, map, file_id)) })(); match res { - Ok((subtree, file_id)) => { - ExpandResult::ok(ExpandedEager { subtree, included_file: Some(file_id) }) + Ok((subtree, map, file_id)) => { + ExpandResult::ok(ExpandedEager { subtree, included_file: Some((file_id, map)) }) } - Err(e) => ExpandResult::with_err( + Err(e) => ExpandResult::new( ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, e, ), @@ -588,7 +716,7 @@ fn include_bytes_expand( tt: &tt::Subtree, ) -> ExpandResult { if let Err(e) = parse_string(tt) { - return ExpandResult::with_err( + return ExpandResult::new( ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, e, ); @@ -613,7 +741,7 @@ fn include_str_expand( let path = match parse_string(tt) { Ok(it) => it, Err(e) => { - return ExpandResult::with_err( + return ExpandResult::new( ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, e, ) @@ -650,7 +778,7 @@ fn env_expand( let key = match parse_string(tt) { Ok(it) => it, Err(e) => { - return ExpandResult::with_err( + return ExpandResult::new( ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, e, ) @@ -686,16 +814,16 @@ fn option_env_expand( let key = match parse_string(tt) { Ok(it) => it, Err(e) => { - return ExpandResult::with_err( + return ExpandResult::new( ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, e, ) } }; - + // FIXME: Use `DOLLAR_CRATE` when that works in eager macros. let expanded = match get_env_inner(db, arg_id, &key) { - None => quote! { #DOLLAR_CRATE::option::Option::None::<&str> }, - Some(s) => quote! { #DOLLAR_CRATE::option::Option::Some(#s) }, + None => quote! { ::core::option::Option::None::<&str> }, + Some(s) => quote! { ::core::option::Option::Some(#s) }, }; ExpandResult::ok(ExpandedEager::new(expanded)) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 45572499e842..965dfa824d8f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -1,22 +1,22 @@ //! Defines database & queries for macro expansion. -use std::sync::Arc; - -use base_db::{salsa, SourceDatabase}; +use base_db::{salsa, Edition, SourceDatabase}; use either::Either; use limit::Limit; use mbe::syntax_node_to_token_tree; use rustc_hash::FxHashSet; use syntax::{ ast::{self, HasAttrs, HasDocComments}, - AstNode, GreenNode, Parse, SyntaxNode, SyntaxToken, T, + AstNode, GreenNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, }; +use triomphe::Arc; use crate::{ - ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion, fixup, - hygiene::HygieneFrame, tt, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, - ExpandError, ExpandResult, ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, - MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, + ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion, + builtin_fn_macro::EagerExpander, fixup, hygiene::HygieneFrame, tt, BuiltinAttrExpander, + BuiltinDeriveExpander, BuiltinFnLikeExpander, ExpandError, ExpandResult, ExpandTo, HirFileId, + HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, + ProcMacroExpander, }; /// Total limit on the number of tokens produced by any macro invocation. @@ -33,6 +33,8 @@ pub enum TokenExpander { DeclarativeMacro { mac: mbe::DeclarativeMacro, def_site_token_map: mbe::TokenMap }, /// Stuff like `line!` and `file!`. Builtin(BuiltinFnLikeExpander), + /// Built-in eagerly expanded fn-like macros (`include!`, `concat!`, etc.) + BuiltinEager(EagerExpander), /// `global_allocator` and such. BuiltinAttr(BuiltinAttrExpander), /// `derive(Copy)` and such. @@ -51,6 +53,9 @@ fn expand( match self { TokenExpander::DeclarativeMacro { mac, .. } => mac.expand(tt).map_err(Into::into), TokenExpander::Builtin(it) => it.expand(db, id, tt).map_err(Into::into), + TokenExpander::BuiltinEager(it) => { + it.expand(db, id, tt).map_err(Into::into).map(|res| res.subtree) + } TokenExpander::BuiltinAttr(it) => it.expand(db, id, tt), TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt), TokenExpander::ProcMacro(_) => { @@ -66,6 +71,7 @@ pub(crate) fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId { match self { TokenExpander::DeclarativeMacro { mac, .. } => mac.map_id_down(id), TokenExpander::Builtin(..) + | TokenExpander::BuiltinEager(..) | TokenExpander::BuiltinAttr(..) | TokenExpander::BuiltinDerive(..) | TokenExpander::ProcMacro(..) => id, @@ -76,6 +82,7 @@ pub(crate) fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) { match self { TokenExpander::DeclarativeMacro { mac, .. } => mac.map_id_up(id), TokenExpander::Builtin(..) + | TokenExpander::BuiltinEager(..) | TokenExpander::BuiltinAttr(..) | TokenExpander::BuiltinDerive(..) | TokenExpander::ProcMacro(..) => (id, mbe::Origin::Call), @@ -90,12 +97,15 @@ pub trait ExpandDatabase: SourceDatabase { /// Main public API -- parses a hir file, not caring whether it's a real /// file or a macro expansion. #[salsa::transparent] - fn parse_or_expand(&self, file_id: HirFileId) -> Option; + fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode; + #[salsa::transparent] + fn parse_or_expand_with_err(&self, file_id: HirFileId) -> ExpandResult>; /// Implementation for the macro case. + // This query is LRU cached fn parse_macro_expansion( &self, macro_file: MacroFile, - ) -> ExpandResult, Arc)>>; + ) -> ExpandResult<(Parse, Arc)>; /// Macro ids. That's probably the tricksiest bit in rust-analyzer, and the /// reason why we use salsa at all. @@ -119,15 +129,19 @@ fn macro_arg( /// just fetches procedural ones. fn macro_def(&self, id: MacroDefId) -> Result, mbe::ParseError>; - /// Expand macro call to a token tree. This query is LRUed (we keep 128 or so results in memory) - fn macro_expand(&self, macro_call: MacroCallId) -> ExpandResult>>; + /// Expand macro call to a token tree. + // This query is LRU cached + fn macro_expand(&self, macro_call: MacroCallId) -> ExpandResult>; /// Special case of the previous query for procedural macros. We can't LRU /// proc macros, since they are not deterministic in general, and - /// non-determinism breaks salsa in a very, very, very bad way. @edwin0cheng - /// heroically debugged this once! + /// non-determinism breaks salsa in a very, very, very bad way. + /// @edwin0cheng heroically debugged this once! fn expand_proc_macro(&self, call: MacroCallId) -> ExpandResult; - /// Firewall query that returns the error from the `macro_expand` query. - fn macro_expand_error(&self, macro_call: MacroCallId) -> Option; + /// Firewall query that returns the errors from the `parse_macro_expansion` query. + fn parse_macro_expansion_error( + &self, + macro_call: MacroCallId, + ) -> ExpandResult>; fn hygiene_frame(&self, file_id: HirFileId) -> Arc; } @@ -159,8 +173,8 @@ pub fn expand_speculative( ); let (attr_arg, token_id) = match loc.kind { - MacroCallKind::Attr { invoc_attr_index, is_derive, .. } => { - let attr = if is_derive { + MacroCallKind::Attr { invoc_attr_index, .. } => { + let attr = if loc.def.is_attribute_derive() { // for pseudo-derive expansion we actually pass the attribute itself only ast::Attr::cast(speculative_args.clone()) } else { @@ -236,17 +250,26 @@ pub fn expand_speculative( } fn ast_id_map(db: &dyn ExpandDatabase, file_id: HirFileId) -> Arc { - let map = db.parse_or_expand(file_id).map(|it| AstIdMap::from_source(&it)).unwrap_or_default(); - Arc::new(map) + Arc::new(AstIdMap::from_source(&db.parse_or_expand(file_id))) } -fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> Option { +fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> SyntaxNode { match file_id.repr() { - HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), + HirFileIdRepr::FileId(file_id) => db.parse(file_id).tree().syntax().clone(), HirFileIdRepr::MacroFile(macro_file) => { - // FIXME: Note how we convert from `Parse` to `SyntaxNode` here, - // forgetting about parse errors. - db.parse_macro_expansion(macro_file).value.map(|(it, _)| it.syntax_node()) + db.parse_macro_expansion(macro_file).value.0.syntax_node() + } + } +} + +fn parse_or_expand_with_err( + db: &dyn ExpandDatabase, + file_id: HirFileId, +) -> ExpandResult> { + match file_id.repr() { + HirFileIdRepr::FileId(file_id) => ExpandResult::ok(db.parse(file_id).to_syntax()), + HirFileIdRepr::MacroFile(macro_file) => { + db.parse_macro_expansion(macro_file).map(|(it, _)| it) } } } @@ -254,35 +277,34 @@ fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> Option ExpandResult, Arc)>> { +) -> ExpandResult<(Parse, Arc)> { let _p = profile::span("parse_macro_expansion"); - let mbe::ValueResult { value, err } = db.macro_expand(macro_file.macro_call_id); + let mbe::ValueResult { value: tt, err } = db.macro_expand(macro_file.macro_call_id); if let Some(err) = &err { - // Note: - // The final goal we would like to make all parse_macro success, - // such that the following log will not call anyway. - let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - let node = loc.kind.to_node(db); + if tracing::enabled!(tracing::Level::DEBUG) { + // Note: + // The final goal we would like to make all parse_macro success, + // such that the following log will not call anyway. + let loc = db.lookup_intern_macro_call(macro_file.macro_call_id); + let node = loc.to_node(db); - // collect parent information for warning log - let parents = - std::iter::successors(loc.kind.file_id().call_node(db), |it| it.file_id.call_node(db)) - .map(|n| format!("{:#}", n.value)) - .collect::>() - .join("\n"); + // collect parent information for warning log + let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| { + it.file_id.call_node(db) + }) + .map(|n| format!("{:#}", n.value)) + .collect::>() + .join("\n"); - tracing::debug!( - "fail on macro_parse: (reason: {:?} macro_call: {:#}) parents: {}", - err, - node.value, - parents - ); + tracing::debug!( + "fail on macro_parse: (reason: {:?} macro_call: {:#}) parents: {}", + err, + node.value, + parents + ); + } } - let tt = match value { - Some(tt) => tt, - None => return ExpandResult { value: None, err }, - }; let expand_to = macro_expand_to(db, macro_file.macro_call_id); @@ -291,7 +313,7 @@ fn parse_macro_expansion( let (parse, rev_token_map) = token_tree_to_syntax_node(&tt, expand_to); - ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err } + ExpandResult { value: (parse, Arc::new(rev_token_map)), err } } fn macro_arg( @@ -339,7 +361,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet return None, + MacroCallKind::Attr { .. } if loc.def.is_attribute_derive() => return None, MacroCallKind::Attr { invoc_attr_index, .. } => { cov_mark::hit!(attribute_macro_attr_censoring); ast::Item::cast(node.clone())? @@ -385,13 +407,14 @@ fn macro_def( ) -> Result, mbe::ParseError> { match id.kind { MacroDefKind::Declarative(ast_id) => { + let is_2021 = db.crate_graph()[id.krate].edition >= Edition::Edition2021; let (mac, def_site_token_map) = match ast_id.to_node(db) { ast::Macro::MacroRules(macro_rules) => { let arg = macro_rules .token_tree() .ok_or_else(|| mbe::ParseError::Expected("expected a token tree".into()))?; let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax()); - let mac = mbe::DeclarativeMacro::parse_macro_rules(&tt)?; + let mac = mbe::DeclarativeMacro::parse_macro_rules(&tt, is_2021)?; (mac, def_site_token_map) } ast::Macro::MacroDef(macro_def) => { @@ -399,7 +422,7 @@ fn macro_def( .body() .ok_or_else(|| mbe::ParseError::Expected("expected a token tree".into()))?; let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax()); - let mac = mbe::DeclarativeMacro::parse_macro2(&tt)?; + let mac = mbe::DeclarativeMacro::parse_macro2(&tt, is_2021)?; (mac, def_site_token_map) } }; @@ -412,82 +435,96 @@ fn macro_def( MacroDefKind::BuiltInDerive(expander, _) => { Ok(Arc::new(TokenExpander::BuiltinDerive(expander))) } - MacroDefKind::BuiltInEager(..) => { - // FIXME: Return a random error here just to make the types align. - // This obviously should do something real instead. - Err(mbe::ParseError::UnexpectedToken("unexpected eager macro".into())) + MacroDefKind::BuiltInEager(expander, ..) => { + Ok(Arc::new(TokenExpander::BuiltinEager(expander))) } MacroDefKind::ProcMacro(expander, ..) => Ok(Arc::new(TokenExpander::ProcMacro(expander))), } } -fn macro_expand( - db: &dyn ExpandDatabase, - id: MacroCallId, -) -> ExpandResult>> { +fn macro_expand(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult> { let _p = profile::span("macro_expand"); - let loc: MacroCallLoc = db.lookup_intern_macro_call(id); + let loc = db.lookup_intern_macro_call(id); if let Some(eager) = &loc.eager { - return ExpandResult { - value: Some(eager.arg_or_expansion.clone()), - // FIXME: There could be errors here! - err: None, - }; + return ExpandResult { value: eager.arg_or_expansion.clone(), err: eager.error.clone() }; } - let macro_arg = match db.macro_arg(id) { - Some(it) => it, - None => { - return ExpandResult::only_err(ExpandError::Other( - "Failed to lower macro args to token tree".into(), - )) - } - }; - let expander = match db.macro_def(loc.def) { Ok(it) => it, // FIXME: This is weird -- we effectively report macro *definition* // errors lazily, when we try to expand the macro. Instead, they should - // be reported at the definition site (when we construct a def map). + // be reported at the definition site when we construct a def map. + // (Note we do report them also at the definition site in the late diagnostic pass) Err(err) => { - return ExpandResult::only_err(ExpandError::Other( - format!("invalid macro definition: {err}").into(), - )) + return ExpandResult { + value: Arc::new(tt::Subtree { + delimiter: tt::Delimiter::UNSPECIFIED, + token_trees: vec![], + }), + err: Some(ExpandError::Other(format!("invalid macro definition: {err}").into())), + } } }; + let Some(macro_arg) = db.macro_arg(id) else { + return ExpandResult { + value: Arc::new( + tt::Subtree { + delimiter: tt::Delimiter::UNSPECIFIED, + token_trees: Vec::new(), + }, + ), + err: Some(ExpandError::Other( + "invalid token tree" + .into(), + )), + }; + }; let ExpandResult { value: mut tt, err } = expander.expand(db, id, ¯o_arg.0); // Set a hard limit for the expanded tt let count = tt.count(); if TOKEN_LIMIT.check(count).is_err() { - return ExpandResult::only_err(ExpandError::Other( - format!( - "macro invocation exceeds token limit: produced {} tokens, limit is {}", - count, - TOKEN_LIMIT.inner(), - ) - .into(), - )); + return ExpandResult { + value: Arc::new(tt::Subtree { + delimiter: tt::Delimiter::UNSPECIFIED, + token_trees: vec![], + }), + err: Some(ExpandError::Other( + format!( + "macro invocation exceeds token limit: produced {} tokens, limit is {}", + count, + TOKEN_LIMIT.inner(), + ) + .into(), + )), + }; } fixup::reverse_fixups(&mut tt, ¯o_arg.1, ¯o_arg.2); - ExpandResult { value: Some(Arc::new(tt)), err } + ExpandResult { value: Arc::new(tt), err } } -fn macro_expand_error(db: &dyn ExpandDatabase, macro_call: MacroCallId) -> Option { - db.macro_expand(macro_call).err +fn parse_macro_expansion_error( + db: &dyn ExpandDatabase, + macro_call_id: MacroCallId, +) -> ExpandResult> { + db.parse_macro_expansion(MacroFile { macro_call_id }) + .map(|it| it.0.errors().to_vec().into_boxed_slice()) } fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult { - let loc: MacroCallLoc = db.lookup_intern_macro_call(id); - let macro_arg = match db.macro_arg(id) { - Some(it) => it, - None => { - return ExpandResult::with_err( - tt::Subtree::empty(), - ExpandError::Other("No arguments for proc-macro".into()), - ) - } + let loc = db.lookup_intern_macro_call(id); + let Some(macro_arg) = db.macro_arg(id) else { + return ExpandResult { + value: tt::Subtree { + delimiter: tt::Delimiter::UNSPECIFIED, + token_trees: Vec::new(), + }, + err: Some(ExpandError::Other( + "invalid token tree" + .into(), + )), + }; }; let expander = match loc.def.kind { @@ -512,8 +549,7 @@ fn hygiene_frame(db: &dyn ExpandDatabase, file_id: HirFileId) -> Arc ExpandTo { - let loc: MacroCallLoc = db.lookup_intern_macro_call(id); - loc.kind.expand_to() + db.lookup_intern_macro_call(id).expand_to() } fn token_tree_to_syntax_node( diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs index aca41b11f926..59a92ff0ab61 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -18,10 +18,9 @@ //! //! //! See the full discussion : -use std::sync::Arc; - use base_db::CrateId; -use syntax::{ted, SyntaxNode}; +use syntax::{ted, Parse, SyntaxNode}; +use triomphe::Arc; use crate::{ ast::{self, AstNode}, @@ -32,77 +31,16 @@ MacroCallLoc, MacroDefId, MacroDefKind, UnresolvedMacro, }; -#[derive(Debug)] -pub struct ErrorEmitted { - _private: (), -} - -pub trait ErrorSink { - fn emit(&mut self, err: ExpandError); - - fn option( - &mut self, - opt: Option, - error: impl FnOnce() -> ExpandError, - ) -> Result { - match opt { - Some(it) => Ok(it), - None => { - self.emit(error()); - Err(ErrorEmitted { _private: () }) - } - } - } - - fn option_with( - &mut self, - opt: impl FnOnce() -> Option, - error: impl FnOnce() -> ExpandError, - ) -> Result { - self.option(opt(), error) - } - - fn result(&mut self, res: Result) -> Result { - match res { - Ok(it) => Ok(it), - Err(e) => { - self.emit(e); - Err(ErrorEmitted { _private: () }) - } - } - } - - fn expand_result_option(&mut self, res: ExpandResult>) -> Result { - match (res.value, res.err) { - (None, Some(err)) => { - self.emit(err); - Err(ErrorEmitted { _private: () }) - } - (Some(value), opt_err) => { - if let Some(err) = opt_err { - self.emit(err); - } - Ok(value) - } - (None, None) => unreachable!("`ExpandResult` without value or error"), - } - } -} - -impl ErrorSink for &'_ mut dyn FnMut(ExpandError) { - fn emit(&mut self, err: ExpandError) { - self(err); - } -} - pub fn expand_eager_macro( db: &dyn ExpandDatabase, krate: CrateId, macro_call: InFile, def: MacroDefId, resolver: &dyn Fn(ModPath) -> Option, - diagnostic_sink: &mut dyn FnMut(ExpandError), -) -> Result, UnresolvedMacro> { +) -> Result>, UnresolvedMacro> { + let MacroDefKind::BuiltInEager(eager, _) = def.kind else { + panic!("called `expand_eager_macro` on non-eager macro def {def:?}") + }; let hygiene = Hygiene::new(db, macro_call.file_id); let parsed_args = macro_call .value @@ -115,60 +53,54 @@ pub fn expand_eager_macro( let expand_to = ExpandTo::from_call_site(¯o_call.value); // Note: - // When `lazy_expand` is called, its *parent* file must be already exists. - // Here we store an eager macro id for the argument expanded subtree here + // When `lazy_expand` is called, its *parent* file must already exist. + // Here we store an eager macro id for the argument expanded subtree // for that purpose. let arg_id = db.intern_macro_call(MacroCallLoc { def, krate, - eager: Some(EagerCallInfo { + eager: Some(Box::new(EagerCallInfo { arg_or_expansion: Arc::new(parsed_args.clone()), included_file: None, - }), + error: None, + })), kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr }, }); let parsed_args = mbe::token_tree_to_syntax_node(&parsed_args, mbe::TopEntryPoint::Expr).0; - let result = match eager_macro_recur( + let ExpandResult { value, mut err } = eager_macro_recur( db, &hygiene, InFile::new(arg_id.as_file(), parsed_args.syntax_node()), krate, resolver, - diagnostic_sink, - ) { - Ok(Ok(it)) => it, - Ok(Err(err)) => return Ok(Err(err)), - Err(err) => return Err(err), + )?; + let Some(value ) = value else { + return Ok(ExpandResult { value: None, err }) + }; + let subtree = { + let mut subtree = mbe::syntax_node_to_token_tree(&value).0; + subtree.delimiter = crate::tt::Delimiter::unspecified(); + subtree }; - let subtree = to_subtree(&result); - if let MacroDefKind::BuiltInEager(eager, _) = def.kind { - let res = eager.expand(db, arg_id, &subtree); - if let Some(err) = res.err { - diagnostic_sink(err); - } - - let loc = MacroCallLoc { - def, - krate, - eager: Some(EagerCallInfo { - arg_or_expansion: Arc::new(res.value.subtree), - included_file: res.value.included_file, - }), - kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, - }; - - Ok(Ok(db.intern_macro_call(loc))) - } else { - panic!("called `expand_eager_macro` on non-eager macro def {def:?}"); + let res = eager.expand(db, arg_id, &subtree); + if err.is_none() { + err = res.err; } -} -fn to_subtree(node: &SyntaxNode) -> crate::tt::Subtree { - let mut subtree = mbe::syntax_node_to_token_tree(node).0; - subtree.delimiter = crate::tt::Delimiter::unspecified(); - subtree + let loc = MacroCallLoc { + def, + krate, + eager: Some(Box::new(EagerCallInfo { + arg_or_expansion: Arc::new(res.value.subtree), + included_file: res.value.included_file, + error: err.clone(), + })), + kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, + }; + + Ok(ExpandResult { value: Some(db.intern_macro_call(loc)), err }) } fn lazy_expand( @@ -176,7 +108,7 @@ fn lazy_expand( def: &MacroDefId, macro_call: InFile, krate: CrateId, -) -> ExpandResult>> { +) -> ExpandResult>> { let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value); let expand_to = ExpandTo::from_call_site(¯o_call.value); @@ -186,10 +118,8 @@ fn lazy_expand( MacroCallKind::FnLike { ast_id: macro_call.with_value(ast_id), expand_to }, ); - let err = db.macro_expand_error(id); - let value = db.parse_or_expand(id.as_file()).map(|node| InFile::new(id.as_file(), node)); - - ExpandResult { value, err } + let file_id = id.as_file(); + db.parse_or_expand_with_err(file_id).map(|parse| InFile::new(file_id, parse)) } fn eager_macro_recur( @@ -198,23 +128,25 @@ fn eager_macro_recur( curr: InFile, krate: CrateId, macro_resolver: &dyn Fn(ModPath) -> Option, - mut diagnostic_sink: &mut dyn FnMut(ExpandError), -) -> Result, UnresolvedMacro> { +) -> Result>, UnresolvedMacro> { let original = curr.value.clone_for_update(); let children = original.descendants().filter_map(ast::MacroCall::cast); let mut replacements = Vec::new(); + // Note: We only report a single error inside of eager expansions + let mut error = None; + // Collect replacement for child in children { let def = match child.path().and_then(|path| ModPath::from_src(db, path, hygiene)) { Some(path) => macro_resolver(path.clone()).ok_or(UnresolvedMacro { path })?, None => { - diagnostic_sink(ExpandError::Other("malformed macro invocation".into())); + error = Some(ExpandError::Other("malformed macro invocation".into())); continue; } }; - let insert = match def.kind { + let ExpandResult { value, err } = match def.kind { MacroDefKind::BuiltInEager(..) => { let id = match expand_eager_macro( db, @@ -222,45 +154,49 @@ fn eager_macro_recur( curr.with_value(child.clone()), def, macro_resolver, - diagnostic_sink, ) { - Ok(Ok(it)) => it, - Ok(Err(err)) => return Ok(Err(err)), + Ok(it) => it, Err(err) => return Err(err), }; - db.parse_or_expand(id.as_file()) - .expect("successful macro expansion should be parseable") - .clone_for_update() + id.map(|call| { + call.map(|call| db.parse_or_expand(call.as_file()).clone_for_update()) + }) } MacroDefKind::Declarative(_) | MacroDefKind::BuiltIn(..) | MacroDefKind::BuiltInAttr(..) | MacroDefKind::BuiltInDerive(..) | MacroDefKind::ProcMacro(..) => { - let res = lazy_expand(db, &def, curr.with_value(child.clone()), krate); - let val = match diagnostic_sink.expand_result_option(res) { - Ok(it) => it, - Err(err) => return Ok(Err(err)), - }; + let ExpandResult { value, err } = + lazy_expand(db, &def, curr.with_value(child.clone()), krate); // replace macro inside - let hygiene = Hygiene::new(db, val.file_id); - match eager_macro_recur(db, &hygiene, val, krate, macro_resolver, diagnostic_sink) { - Ok(Ok(it)) => it, - Ok(Err(err)) => return Ok(Err(err)), - Err(err) => return Err(err), - } + let hygiene = Hygiene::new(db, value.file_id); + let ExpandResult { value, err: error } = eager_macro_recur( + db, + &hygiene, + // FIXME: We discard parse errors here + value.map(|it| it.syntax_node()), + krate, + macro_resolver, + )?; + let err = if err.is_none() { error } else { err }; + ExpandResult { value, err } } }; - + if err.is_some() { + error = err; + } // check if the whole original syntax is replaced if child.syntax() == &original { - return Ok(Ok(insert)); + return Ok(ExpandResult { value, err: error }); } - replacements.push((child, insert)); + if let Some(insert) = value { + replacements.push((child, insert)); + } } replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new)); - Ok(Ok(original)) + Ok(ExpandResult { value: Some(original), err: error }) } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index b273f21768c6..00796e7c0dbb 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -14,7 +14,7 @@ /// The result of calculating fixes for a syntax node -- a bunch of changes /// (appending to and replacing nodes), the information that is needed to /// reverse those changes afterwards, and a token map. -#[derive(Debug)] +#[derive(Debug, Default)] pub(crate) struct SyntaxFixups { pub(crate) append: FxHashMap>, pub(crate) replace: FxHashMap>, @@ -24,7 +24,7 @@ pub(crate) struct SyntaxFixups { } /// This is the information needed to reverse the fixups. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct SyntaxFixupUndoInfo { original: Vec, } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs index 2eb56fc9e8b2..10f8fe9cec42 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -2,8 +2,6 @@ //! //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at //! this moment, this is horribly incomplete and handles only `$crate`. -use std::sync::Arc; - use base_db::CrateId; use db::TokenExpander; use either::Either; @@ -12,6 +10,7 @@ ast::{self, HasDocComments}, AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, }; +use triomphe::Arc; use crate::{ db::{self, ExpandDatabase}, @@ -200,8 +199,14 @@ fn make_hygiene_info( }); let macro_def = db.macro_def(loc.def).ok()?; - let (_, exp_map) = db.parse_macro_expansion(macro_file).value?; - let macro_arg = db.macro_arg(macro_file.macro_call_id)?; + let (_, exp_map) = db.parse_macro_expansion(macro_file).value; + let macro_arg = db.macro_arg(macro_file.macro_call_id).unwrap_or_else(|| { + Arc::new(( + tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() }, + Default::default(), + Default::default(), + )) + }); Some(HygieneInfo { file: macro_file, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 5e99eacc1b61..c8373778d32b 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -20,11 +20,13 @@ pub mod attrs; mod fixup; +use mbe::TokenMap; pub use mbe::{Origin, ValueResult}; use ::tt::token_id as tt; +use triomphe::Arc; -use std::{fmt, hash::Hash, iter, sync::Arc}; +use std::{fmt, hash::Hash, iter}; use base_db::{ impl_intern_key, @@ -51,11 +53,11 @@ pub type ExpandResult = ValueResult; -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum ExpandError { UnresolvedProcMacro(CrateId), Mbe(mbe::ExpandError), - RecursionOverflowPosioned, + RecursionOverflowPoisoned, Other(Box), } @@ -70,7 +72,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"), ExpandError::Mbe(it) => it.fmt(f), - ExpandError::RecursionOverflowPosioned => { + ExpandError::RecursionOverflowPoisoned => { f.write_str("overflow expanding the original macro") } ExpandError::Other(it) => f.write_str(it), @@ -113,7 +115,7 @@ pub struct MacroFile { pub struct MacroCallLoc { pub def: MacroDefId, pub(crate) krate: CrateId, - eager: Option, + eager: Option>, pub kind: MacroCallKind, } @@ -139,7 +141,8 @@ pub enum MacroDefKind { struct EagerCallInfo { /// NOTE: This can be *either* the expansion result, *or* the argument to the eager macro! arg_or_expansion: Arc, - included_file: Option, + included_file: Option<(FileId, TokenMap)>, + error: Option, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -166,8 +169,6 @@ pub enum MacroCallKind { /// Outer attributes are counted first, then inner attributes. This does not support /// out-of-line modules, which may have attributes spread across 2 files! invoc_attr_index: AttrId, - /// Whether this attribute is the `#[derive]` attribute. - is_derive: bool, }, } @@ -205,8 +206,8 @@ pub fn original_file(self, db: &dyn db::ExpandDatabase) -> FileId { HirFileIdRepr::FileId(id) => break id, HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_call_id); - file_id = match loc.eager { - Some(EagerCallInfo { included_file: Some(file), .. }) => file.into(), + file_id = match loc.eager.as_deref() { + Some(&EagerCallInfo { included_file: Some((file, _)), .. }) => file.into(), _ => loc.kind.file_id(), }; } @@ -230,18 +231,17 @@ pub fn expansion_level(self, db: &dyn db::ExpandDatabase) -> u32 { pub fn call_node(self, db: &dyn db::ExpandDatabase) -> Option> { let macro_file = self.macro_file()?; let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - Some(loc.kind.to_node(db)) + Some(loc.to_node(db)) } /// If this is a macro call, returns the syntax node of the very first macro call this file resides in. pub fn original_call_node(self, db: &dyn db::ExpandDatabase) -> Option<(FileId, SyntaxNode)> { - let mut call = - db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).kind.to_node(db); + let mut call = db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).to_node(db); loop { match call.file_id.repr() { HirFileIdRepr::FileId(file_id) => break Some((file_id, call.value)), HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => { - call = db.lookup_intern_macro_call(macro_call_id).kind.to_node(db); + call = db.lookup_intern_macro_call(macro_call_id).to_node(db); } } } @@ -255,8 +255,14 @@ pub fn expansion_info(self, db: &dyn db::ExpandDatabase) -> Option Option loc.kind.to_node(db), + MacroDefKind::BuiltInDerive(..) => loc.to_node(db), _ => return None, }; Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) @@ -319,7 +325,7 @@ pub fn is_include_macro(&self, db: &dyn db::ExpandDatabase) -> bool { match self.macro_file() { Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - matches!(loc.eager, Some(EagerCallInfo { included_file: Some(_), .. })) + matches!(loc.eager.as_deref(), Some(EagerCallInfo { included_file: Some(..), .. })) } _ => false, } @@ -342,7 +348,7 @@ pub fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::ExpandDatabase) -> bo match self.macro_file() { Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - matches!(loc.kind, MacroCallKind::Attr { is_derive: true, .. }) + loc.def.is_attribute_derive() } None => false, } @@ -413,22 +419,15 @@ pub fn is_attribute(&self) -> bool { MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, ProcMacroKind::Attr, _) ) } + + pub fn is_attribute_derive(&self) -> bool { + matches!(self.kind, MacroDefKind::BuiltInAttr(expander, ..) if expander.is_derive()) + } } -// FIXME: attribute indices do not account for nested `cfg_attr` - -impl MacroCallKind { - /// Returns the file containing the macro invocation. - fn file_id(&self) -> HirFileId { - match *self { - MacroCallKind::FnLike { ast_id: InFile { file_id, .. }, .. } - | MacroCallKind::Derive { ast_id: InFile { file_id, .. }, .. } - | MacroCallKind::Attr { ast_id: InFile { file_id, .. }, .. } => file_id, - } - } - +impl MacroCallLoc { pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> InFile { - match self { + match self.kind { MacroCallKind::FnLike { ast_id, .. } => { ast_id.with_value(ast_id.to_node(db).syntax().clone()) } @@ -444,23 +443,49 @@ pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> InFile { .unwrap_or_else(|| it.syntax().clone()) }) } - MacroCallKind::Attr { ast_id, is_derive: true, invoc_attr_index, .. } => { - // FIXME: handle `cfg_attr` - ast_id.with_value(ast_id.to_node(db)).map(|it| { - it.doc_comments_and_attrs() - .nth(invoc_attr_index.ast_index()) - .and_then(|it| match it { - Either::Left(attr) => Some(attr.syntax().clone()), - Either::Right(_) => None, - }) - .unwrap_or_else(|| it.syntax().clone()) - }) + MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { + if self.def.is_attribute_derive() { + // FIXME: handle `cfg_attr` + ast_id.with_value(ast_id.to_node(db)).map(|it| { + it.doc_comments_and_attrs() + .nth(invoc_attr_index.ast_index()) + .and_then(|it| match it { + Either::Left(attr) => Some(attr.syntax().clone()), + Either::Right(_) => None, + }) + .unwrap_or_else(|| it.syntax().clone()) + }) + } else { + ast_id.with_value(ast_id.to_node(db).syntax().clone()) + } } - MacroCallKind::Attr { ast_id, .. } => { - ast_id.with_value(ast_id.to_node(db).syntax().clone()) + } + } + + fn expand_to(&self) -> ExpandTo { + match self.kind { + MacroCallKind::FnLike { expand_to, .. } => expand_to, + MacroCallKind::Derive { .. } => ExpandTo::Items, + MacroCallKind::Attr { .. } if self.def.is_attribute_derive() => ExpandTo::Statements, + MacroCallKind::Attr { .. } => { + // is this always correct? + ExpandTo::Items } } } +} + +// FIXME: attribute indices do not account for nested `cfg_attr` + +impl MacroCallKind { + /// Returns the file containing the macro invocation. + fn file_id(&self) -> HirFileId { + match *self { + MacroCallKind::FnLike { ast_id: InFile { file_id, .. }, .. } + | MacroCallKind::Derive { ast_id: InFile { file_id, .. }, .. } + | MacroCallKind::Attr { ast_id: InFile { file_id, .. }, .. } => file_id, + } + } /// Returns the original file range that best describes the location of this macro call. /// @@ -538,15 +563,6 @@ fn arg(&self, db: &dyn db::ExpandDatabase) -> Option { MacroCallKind::Attr { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()), } } - - fn expand_to(&self) -> ExpandTo { - match self { - MacroCallKind::FnLike { expand_to, .. } => *expand_to, - MacroCallKind::Derive { .. } => ExpandTo::Items, - MacroCallKind::Attr { is_derive: true, .. } => ExpandTo::Statements, - MacroCallKind::Attr { .. } => ExpandTo::Items, // is this always correct? - } - } } impl MacroCallId { @@ -610,7 +626,7 @@ pub fn map_token_down( let token_range = token.value.text_range(); match &loc.kind { - MacroCallKind::Attr { attr_args, invoc_attr_index, is_derive, .. } => { + MacroCallKind::Attr { attr_args, invoc_attr_index, .. } => { // FIXME: handle `cfg_attr` let attr = item .doc_comments_and_attrs() @@ -626,7 +642,8 @@ pub fn map_token_down( token.value.text_range().checked_sub(attr_input_start)?; // shift by the item's tree's max id let token_id = attr_args.1.token_by_range(relative_range)?; - let token_id = if *is_derive { + + let token_id = if loc.def.is_attribute_derive() { // we do not shift for `#[derive]`, as we only need to downmap the derive attribute tokens token_id } else { @@ -677,20 +694,31 @@ pub fn map_token_up( let call_id = self.expanded.file_id.macro_file()?.macro_call_id; let loc = db.lookup_intern_macro_call(call_id); + if let Some((file, map)) = loc.eager.and_then(|e| e.included_file) { + // Special case: map tokens from `include!` expansions to the included file + let range = map.first_range_by_token(token_id, token.value.kind())?; + let source = db.parse(file); + + let token = source.syntax_node().covering_element(range).into_token()?; + + return Some((InFile::new(file.into(), token), Origin::Call)); + } + // Attributes are a bit special for us, they have two inputs, the input tokentree and the annotated item. let (token_map, tt) = match &loc.kind { - MacroCallKind::Attr { attr_args, is_derive: true, .. } => { - (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned()) - } MacroCallKind::Attr { attr_args, .. } => { - // try unshifting the the token id, if unshifting fails, the token resides in the non-item attribute input - // note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this - match self.macro_arg_shift.unshift(token_id) { - Some(unshifted) => { - token_id = unshifted; - (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned()) + if loc.def.is_attribute_derive() { + (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned()) + } else { + // try unshifting the token id, if unshifting fails, the token resides in the non-item attribute input + // note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this + match self.macro_arg_shift.unshift(token_id) { + Some(unshifted) => { + token_id = unshifted; + (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned()) + } + None => (&self.macro_arg.1, self.arg.clone()), } - None => (&self.macro_arg.1, self.arg.clone()), } } _ => match origin { @@ -718,7 +746,7 @@ pub fn map_token_up( impl AstId { pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> N { - let root = db.parse_or_expand(self.file_id).unwrap(); + let root = db.parse_or_expand(self.file_id); db.ast_id_map(self.file_id).get(self.value).to_node(&root) } } @@ -754,7 +782,7 @@ pub fn as_ref(&self) -> InFile<&T> { } pub fn file_syntax(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode { - db.parse_or_expand(self.file_id).expect("source created from invalid file") + db.parse_or_expand(self.file_id) } } @@ -950,6 +978,7 @@ fn ascend_node_border_tokens( let first_token = |node: &SyntaxNode| skip_trivia_token(node.first_token()?, Direction::Next); let last_token = |node: &SyntaxNode| skip_trivia_token(node.last_token()?, Direction::Prev); + // FIXME: Once the token map rewrite is done, this shouldnt need to rely on syntax nodes and tokens anymore let first = first_token(node)?; let last = last_token(node)?; let first = ascend_call_token(db, &expansion, InFile::new(file_id, first))?; @@ -977,6 +1006,7 @@ pub fn descendants(self) -> impl Iterator> { self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n)) } + // FIXME: this should return `Option>` pub fn original_ast_node(self, db: &dyn db::ExpandDatabase) -> Option> { // This kind of upmapping can only be achieved in attribute expanded files, // as we don't have node inputs otherwise and therefore can't find an `N` node in the input diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs index e9393cc89aed..47a8ab7de77f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs @@ -1,7 +1,7 @@ //! A lowering for `use`-paths (more generally, paths without angle-bracketed segments). use std::{ - fmt::{self, Display}, + fmt::{self, Display as _}, iter, }; @@ -24,6 +24,12 @@ pub struct ModPath { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UnescapedModPath<'a>(&'a ModPath); +impl<'a> UnescapedModPath<'a> { + pub fn display(&'a self, db: &'a dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { + UnescapedDisplay { db, path: self } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PathKind { Plain, @@ -110,52 +116,30 @@ pub fn unescaped(&self) -> UnescapedModPath<'_> { UnescapedModPath(self) } - fn _fmt(&self, f: &mut fmt::Formatter<'_>, escaped: bool) -> fmt::Result { - let mut first_segment = true; - let mut add_segment = |s| -> fmt::Result { - if !first_segment { - f.write_str("::")?; - } - first_segment = false; - f.write_str(s)?; - Ok(()) - }; - match self.kind { - PathKind::Plain => {} - PathKind::Super(0) => add_segment("self")?, - PathKind::Super(n) => { - for _ in 0..n { - add_segment("super")?; - } - } - PathKind::Crate => add_segment("crate")?, - PathKind::Abs => add_segment("")?, - PathKind::DollarCrate(_) => add_segment("$crate")?, - } - for segment in &self.segments { - if !first_segment { - f.write_str("::")?; - } - first_segment = false; - if escaped { - segment.fmt(f)? - } else { - segment.unescaped().fmt(f)? - }; - } - Ok(()) + pub fn display<'a>(&'a self, db: &'a dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { + Display { db, path: self } } } -impl Display for ModPath { +struct Display<'a> { + db: &'a dyn ExpandDatabase, + path: &'a ModPath, +} + +impl<'a> fmt::Display for Display<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self._fmt(f, true) + display_fmt_path(self.db, self.path, f, true) } } -impl<'a> Display for UnescapedModPath<'a> { +struct UnescapedDisplay<'a> { + db: &'a dyn ExpandDatabase, + path: &'a UnescapedModPath<'a>, +} + +impl<'a> fmt::Display for UnescapedDisplay<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0._fmt(f, false) + display_fmt_path(self.db, self.path.0, f, false) } } @@ -164,6 +148,46 @@ fn from(name: Name) -> ModPath { ModPath::from_segments(PathKind::Plain, iter::once(name)) } } +fn display_fmt_path( + db: &dyn ExpandDatabase, + path: &ModPath, + f: &mut fmt::Formatter<'_>, + escaped: bool, +) -> fmt::Result { + let mut first_segment = true; + let mut add_segment = |s| -> fmt::Result { + if !first_segment { + f.write_str("::")?; + } + first_segment = false; + f.write_str(s)?; + Ok(()) + }; + match path.kind { + PathKind::Plain => {} + PathKind::Super(0) => add_segment("self")?, + PathKind::Super(n) => { + for _ in 0..n { + add_segment("super")?; + } + } + PathKind::Crate => add_segment("crate")?, + PathKind::Abs => add_segment("")?, + PathKind::DollarCrate(_) => add_segment("$crate")?, + } + for segment in &path.segments { + if !first_segment { + f.write_str("::")?; + } + first_segment = false; + if escaped { + segment.display(db).fmt(f)?; + } else { + segment.unescaped().display(db).fmt(f)?; + } + } + Ok(()) +} fn convert_path( db: &dyn ExpandDatabase, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs index c3462beac73c..f8dbb842775c 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs @@ -24,27 +24,6 @@ enum Repr { TupleField(usize), } -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - Repr::Text(text) => fmt::Display::fmt(&text, f), - Repr::TupleField(idx) => fmt::Display::fmt(&idx, f), - } - } -} - -impl<'a> fmt::Display for UnescapedName<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 .0 { - Repr::Text(text) => { - let text = text.strip_prefix("r#").unwrap_or(text); - fmt::Display::fmt(&text, f) - } - Repr::TupleField(idx) => fmt::Display::fmt(&idx, f), - } - } -} - impl<'a> UnescapedName<'a> { /// Returns the textual representation of this name as a [`SmolStr`]. Prefer using this over /// [`ToString::to_string`] if possible as this conversion is cheaper in the general case. @@ -60,6 +39,11 @@ pub fn to_smol_str(&self) -> SmolStr { Repr::TupleField(it) => SmolStr::new(it.to_string()), } } + + pub fn display(&'a self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { + _ = db; + UnescapedDisplay { name: self } + } } impl Name { @@ -78,7 +62,7 @@ pub fn new_lifetime(lt: &ast::Lifetime) -> Name { Self::new_text(lt.text().into()) } - /// Shortcut to create inline plain text name + /// Shortcut to create inline plain text name. Panics if `text.len() > 22` const fn new_inline(text: &str) -> Name { Name::new_text(SmolStr::new_inline(text)) } @@ -112,6 +96,17 @@ pub const fn missing() -> Name { Name::new_inline("[missing name]") } + /// Generates a new name which is only equal to itself, by incrementing a counter. Due + /// its implementation, it should not be used in things that salsa considers, like + /// type names or field names, and it should be only used in names of local variables + /// and labels and similar things. + pub fn generate_new_name() -> Name { + use std::sync::atomic::{AtomicUsize, Ordering}; + static CNT: AtomicUsize = AtomicUsize::new(0); + let c = CNT.fetch_add(1, Ordering::Relaxed); + Name::new_text(format!("{c}").into()) + } + /// Returns the tuple index this name represents if it is a tuple field. pub fn as_tuple_index(&self) -> Option { match self.0 { @@ -156,6 +151,40 @@ pub fn is_escaped(&self) -> bool { Repr::TupleField(_) => false, } } + + pub fn display<'a>(&'a self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { + _ = db; + Display { name: self } + } +} + +struct Display<'a> { + name: &'a Name, +} + +impl<'a> fmt::Display for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.name.0 { + Repr::Text(text) => fmt::Display::fmt(&text, f), + Repr::TupleField(idx) => fmt::Display::fmt(&idx, f), + } + } +} + +struct UnescapedDisplay<'a> { + name: &'a UnescapedName<'a>, +} + +impl<'a> fmt::Display for UnescapedDisplay<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.name.0 .0 { + Repr::Text(text) => { + let text = text.strip_prefix("r#").unwrap_or(text); + fmt::Display::fmt(&text, f) + } + Repr::TupleField(idx) => fmt::Display::fmt(&idx, f), + } + } } pub trait AsName { @@ -337,18 +366,24 @@ macro_rules! known_names { crate_type, derive, global_allocator, + no_core, + no_std, test, test_case, recursion_limit, feature, // known methods of lang items call_once, + call_mut, + call, eq, ne, ge, gt, le, lt, + // known fields of lang items + pieces, // lang items add_assign, add, @@ -363,6 +398,7 @@ macro_rules! known_names { deref, div_assign, div, + drop, fn_mut, fn_once, future_trait, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs index d758e9302cd8..c9539210abf6 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs @@ -7,20 +7,23 @@ #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct ProcMacroExpander { - proc_macro_id: Option, + proc_macro_id: ProcMacroId, } +const DUMMY_ID: u32 = !0; + impl ProcMacroExpander { pub fn new(proc_macro_id: ProcMacroId) -> Self { - Self { proc_macro_id: Some(proc_macro_id) } + assert_ne!(proc_macro_id.0, DUMMY_ID); + Self { proc_macro_id } } pub fn dummy() -> Self { - Self { proc_macro_id: None } + Self { proc_macro_id: ProcMacroId(DUMMY_ID) } } pub fn is_dummy(&self) -> bool { - self.proc_macro_id.is_none() + self.proc_macro_id.0 == DUMMY_ID } pub fn expand( @@ -32,33 +35,37 @@ pub fn expand( attr_arg: Option<&tt::Subtree>, ) -> ExpandResult { match self.proc_macro_id { - Some(id) => { - let krate_graph = db.crate_graph(); - let proc_macros = match &krate_graph[def_crate].proc_macro { - Ok(proc_macros) => proc_macros, - Err(_) => { + ProcMacroId(DUMMY_ID) => { + ExpandResult::new(tt::Subtree::empty(), ExpandError::UnresolvedProcMacro(def_crate)) + } + ProcMacroId(id) => { + let proc_macros = db.proc_macros(); + let proc_macros = match proc_macros.get(&def_crate) { + Some(Ok(proc_macros)) => proc_macros, + Some(Err(_)) | None => { never!("Non-dummy expander even though there are no proc macros"); - return ExpandResult::with_err( + return ExpandResult::new( tt::Subtree::empty(), ExpandError::Other("Internal error".into()), ); } }; - let proc_macro = match proc_macros.get(id.0 as usize) { + let proc_macro = match proc_macros.get(id as usize) { Some(proc_macro) => proc_macro, None => { never!( "Proc macro index out of bounds: the length is {} but the index is {}", proc_macros.len(), - id.0 + id ); - return ExpandResult::with_err( + return ExpandResult::new( tt::Subtree::empty(), ExpandError::Other("Internal error".into()), ); } }; + let krate_graph = db.crate_graph(); // Proc macros have access to the environment variables of the invoking crate. let env = &krate_graph[calling_crate].env; match proc_macro.expander.expand(tt, attr_arg, env) { @@ -74,17 +81,12 @@ pub fn expand( } } ProcMacroExpansionError::System(text) - | ProcMacroExpansionError::Panic(text) => ExpandResult::with_err( - tt::Subtree::empty(), - ExpandError::Other(text.into()), - ), + | ProcMacroExpansionError::Panic(text) => { + ExpandResult::new(tt::Subtree::empty(), ExpandError::Other(text.into())) + } }, } } - None => ExpandResult::with_err( - tt::Subtree::empty(), - ExpandError::UnresolvedProcMacro(def_crate), - ), } } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs index 63586f9daf06..ab3809abc7a2 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs @@ -162,6 +162,12 @@ fn to_token(self) -> crate::tt::TokenTree { } } +impl ToTokenTree for &crate::tt::TokenTree { + fn to_token(self) -> crate::tt::TokenTree { + self.clone() + } +} + impl ToTokenTree for crate::tt::Subtree { fn to_token(self) -> crate::tt::TokenTree { self.into() diff --git a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml index 9b3296df2508..6ca0dbb85039 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml @@ -15,7 +15,7 @@ doctest = false cov-mark = "2.0.0-pre.1" itertools = "0.10.5" arrayvec = "0.7.2" -bitflags = "1.3.2" +bitflags = "2.1.0" smallvec.workspace = true ena = "0.14.0" either = "1.7.0" @@ -28,6 +28,8 @@ chalk-recursive = { version = "0.89.0", default-features = false } chalk-derive = "0.89.0" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } once_cell = "1.17.0" +triomphe.workspace = true +nohash-hasher.workspace = true typed-arena = "2.0.1" rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs index 58744dd0c0f9..f5b3f176b12e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs @@ -3,12 +3,11 @@ //! reference to a type with the field `bar`. This is an approximation of the //! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs). -use std::sync::Arc; - use chalk_ir::cast::Cast; use hir_def::lang_item::LangItem; use hir_expand::name::name; use limit::Limit; +use triomphe::Arc; use crate::{ db::HirDatabase, infer::unify::InferenceTable, Canonical, Goal, Interner, ProjectionTyExt, @@ -23,6 +22,21 @@ pub(crate) enum AutoderefKind { Overloaded, } +pub fn autoderef( + db: &dyn HirDatabase, + env: Arc, + ty: Canonical, +) -> impl Iterator> + '_ { + let mut table = InferenceTable::new(db, env); + let ty = table.instantiate_canonical(ty); + let mut autoderef = Autoderef::new(&mut table, ty); + let mut v = Vec::new(); + while let Some((ty, _steps)) = autoderef.next() { + v.push(autoderef.table.canonicalize(ty).value); + } + v.into_iter() +} + #[derive(Debug)] pub(crate) struct Autoderef<'a, 'db> { pub(crate) table: &'a mut InferenceTable<'db>, @@ -76,49 +90,43 @@ pub(crate) fn autoderef_step( table: &mut InferenceTable<'_>, ty: Ty, ) -> Option<(AutoderefKind, Ty)> { - if let Some(derefed) = builtin_deref(&ty) { + if let Some(derefed) = builtin_deref(table, &ty, false) { Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed))) } else { Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?)) } } -// FIXME: replace uses of this with Autoderef above -pub fn autoderef( - db: &dyn HirDatabase, - env: Arc, - ty: Canonical, -) -> impl Iterator> + '_ { - let mut table = InferenceTable::new(db, env); - let ty = table.instantiate_canonical(ty); - let mut autoderef = Autoderef::new(&mut table, ty); - let mut v = Vec::new(); - while let Some((ty, _steps)) = autoderef.next() { - v.push(autoderef.table.canonicalize(ty).value); - } - v.into_iter() -} - -pub(crate) fn deref(table: &mut InferenceTable<'_>, ty: Ty) -> Option { - let _p = profile::span("deref"); - autoderef_step(table, ty).map(|(_, ty)| ty) -} - -fn builtin_deref(ty: &Ty) -> Option<&Ty> { +pub(crate) fn builtin_deref<'ty>( + table: &mut InferenceTable<'_>, + ty: &'ty Ty, + explicit: bool, +) -> Option<&'ty Ty> { match ty.kind(Interner) { - TyKind::Ref(.., ty) | TyKind::Raw(.., ty) => Some(ty), + TyKind::Ref(.., ty) => Some(ty), + // FIXME: Maybe accept this but diagnose if its not explicit? + TyKind::Raw(.., ty) if explicit => Some(ty), + &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => { + if crate::lang_items::is_box(table.db, adt) { + substs.at(Interner, 0).ty(Interner) + } else { + None + } + } _ => None, } } -fn deref_by_trait(table: &mut InferenceTable<'_>, ty: Ty) -> Option { +pub(crate) fn deref_by_trait( + table @ &mut InferenceTable { db, .. }: &mut InferenceTable<'_>, + ty: Ty, +) -> Option { let _p = profile::span("deref_by_trait"); if table.resolve_ty_shallow(&ty).inference_var(Interner).is_some() { // don't try to deref unknown variables return None; } - let db = table.db; let deref_trait = db.lang_item(table.trait_env.krate, LangItem::Deref).and_then(|l| l.as_trait())?; let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs index 03e9443599d8..eec57ba3f80f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs @@ -18,7 +18,6 @@ consteval::unknown_const_as_generic, db::HirDatabase, infer::unify::InferenceTable, primitive, 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)] @@ -195,6 +194,19 @@ pub fn placeholder_subst(db: &dyn HirDatabase, def: impl Into) -> params.placeholder_subst(db) } + pub fn unknown_subst(db: &dyn HirDatabase, def: impl Into) -> Substitution { + let params = generics(db.upcast(), def.into()); + Substitution::from_iter( + Interner, + params.iter_id().map(|id| match id { + either::Either::Left(_) => TyKind::Error.intern(Interner).cast(Interner), + either::Either::Right(id) => { + unknown_const_as_generic(db.const_param_ty(id)).cast(Interner) + } + }), + ) + } + pub fn subst_for_def( db: &dyn HirDatabase, def: impl Into, @@ -233,6 +245,25 @@ pub fn subst_for_generator(db: &dyn HirDatabase, parent: DefWithBodyId) -> TyBui TyBuilder::new((), params, parent_subst) } + pub fn subst_for_closure( + db: &dyn HirDatabase, + parent: DefWithBodyId, + sig_ty: Ty, + ) -> Substitution { + let sig_ty = sig_ty.cast(Interner); + let self_subst = iter::once(&sig_ty); + let Some(parent) = parent.as_generic_def_id() else { + return Substitution::from_iter(Interner, self_subst); + }; + Substitution::from_iter( + Interner, + self_subst + .chain(generics(db.upcast(), parent).placeholder_subst(db).iter(Interner)) + .cloned() + .collect::>(), + ) + } + pub fn build(self) -> Substitution { let ((), subst) = self.build_internal(); subst @@ -362,21 +393,4 @@ pub fn def_ty( pub fn impl_self_ty(db: &dyn HirDatabase, def: hir_def::ImplId) -> TyBuilder> { TyBuilder::subst_for_def(db, def, None).with_data(db.impl_self_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/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs index 28ae4c349f83..ac962c9e3e1f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs @@ -1,8 +1,8 @@ //! The implementation of `RustIrDatabase` for Chalk, which provides information //! about the code that Chalk needs. -use std::sync::Arc; +use core::ops; +use std::{iter, sync::Arc}; -use cov_mark::hit; use tracing::debug; use chalk_ir::{cast::Cast, fold::shift::Shift, CanonicalVarKinds}; @@ -10,9 +10,9 @@ use base_db::CrateId; use hir_def::{ - expr::Movability, + hir::Movability, lang_item::{lang_attr, LangItem, LangItemTarget}, - AssocItemId, GenericDefId, HasModule, ItemContainerId, Lookup, ModuleId, TypeAliasId, + AssocItemId, BlockId, GenericDefId, HasModule, ItemContainerId, Lookup, TypeAliasId, }; use hir_expand::name::name; @@ -25,7 +25,7 @@ method_resolution::{TraitImpls, TyFingerprint, ALL_FLOAT_FPS, ALL_INT_FPS}, to_assoc_type_id, to_chalk_trait_id, traits::ChalkContext, - utils::generics, + utils::{generics, ClosureSubst}, wrap_empty_binders, AliasEq, AliasTy, BoundVar, CallableDefId, DebruijnIndex, FnDefId, Interner, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, WhereClause, @@ -108,17 +108,6 @@ fn binder_kind( _ => self_ty_fp.as_ref().map(std::slice::from_ref).unwrap_or(&[]), }; - fn local_impls(db: &dyn HirDatabase, module: ModuleId) -> Option> { - let block = module.containing_block()?; - hit!(block_local_impls); - db.trait_impls_in_block(block) - } - - // Note: Since we're using impls_for_trait, only impls where the trait - // can be resolved should ever reach Chalk. impl_datum relies on that - // and will panic if the trait can't be resolved. - let in_deps = self.db.trait_impls_in_deps(self.krate); - let in_self = self.db.trait_impls_in_crate(self.krate); let trait_module = trait_.module(self.db.upcast()); let type_module = match self_ty_fp { Some(TyFingerprint::Adt(adt_id)) => Some(adt_id.module(self.db.upcast())), @@ -128,33 +117,62 @@ fn local_impls(db: &dyn HirDatabase, module: ModuleId) -> Option Some(TyFingerprint::Dyn(trait_id)) => Some(trait_id.module(self.db.upcast())), _ => None, }; - let impl_maps = [ - Some(in_deps), - Some(in_self), - local_impls(self.db, trait_module), - type_module.and_then(|m| local_impls(self.db, m)), - ]; + + let mut def_blocks = + [trait_module.containing_block(), type_module.and_then(|it| it.containing_block())]; + + // Note: Since we're using impls_for_trait, only impls where the trait + // can be resolved should ever reach Chalk. impl_datum relies on that + // and will panic if the trait can't be resolved. + let in_deps = self.db.trait_impls_in_deps(self.krate); + let in_self = self.db.trait_impls_in_crate(self.krate); + + let block_impls = iter::successors(self.block, |&block_id| { + cov_mark::hit!(block_local_impls); + self.db.block_def_map(block_id).parent().and_then(|module| module.containing_block()) + }) + .inspect(|&block_id| { + // make sure we don't search the same block twice + def_blocks.iter_mut().for_each(|block| { + if *block == Some(block_id) { + *block = None; + } + }); + }) + .map(|block_id| self.db.trait_impls_in_block(block_id)); let id_to_chalk = |id: hir_def::ImplId| id.to_chalk(self.db); - - let result: Vec<_> = if fps.is_empty() { - debug!("Unrestricted search for {:?} impls...", trait_); - impl_maps - .iter() - .filter_map(|o| o.as_ref()) - .flat_map(|impls| impls.for_trait(trait_).map(id_to_chalk)) - .collect() - } else { - impl_maps - .iter() - .filter_map(|o| o.as_ref()) - .flat_map(|impls| { - fps.iter().flat_map(move |fp| { - impls.for_trait_and_self_ty(trait_, *fp).map(id_to_chalk) - }) - }) - .collect() - }; + let mut result = vec![]; + match fps { + [] => { + debug!("Unrestricted search for {:?} impls...", trait_); + let mut f = |impls: &TraitImpls| { + result.extend(impls.for_trait(trait_).map(id_to_chalk)); + }; + f(&in_self); + in_deps.iter().map(ops::Deref::deref).for_each(&mut f); + block_impls.for_each(|it| f(&it)); + def_blocks + .into_iter() + .flatten() + .for_each(|it| f(&self.db.trait_impls_in_block(it))); + } + fps => { + let mut f = + |impls: &TraitImpls| { + result.extend(fps.iter().flat_map(|fp| { + impls.for_trait_and_self_ty(trait_, *fp).map(id_to_chalk) + })); + }; + f(&in_self); + in_deps.iter().map(ops::Deref::deref).for_each(&mut f); + block_impls.for_each(|it| f(&it)); + def_blocks + .into_iter() + .flatten() + .for_each(|it| f(&self.db.trait_impls_in_block(it))); + } + } debug!("impls_for_trait returned {} impls", result.len()); result @@ -193,7 +211,7 @@ fn program_clauses_for_env( &self, environment: &chalk_ir::Environment, ) -> chalk_ir::ProgramClauses { - self.db.program_clauses_for_chalk_env(self.krate, environment.clone()) + self.db.program_clauses_for_chalk_env(self.krate, self.block, environment.clone()) } fn opaque_ty_data(&self, id: chalk_ir::OpaqueTyId) -> Arc { @@ -321,7 +339,7 @@ fn closure_inputs_and_output( _closure_id: chalk_ir::ClosureId, substs: &chalk_ir::Substitution, ) -> chalk_ir::Binders> { - let sig_ty = substs.at(Interner, 0).assert_ty_ref(Interner).clone(); + let sig_ty = ClosureSubst(substs).sig_ty(); let sig = &sig_ty.callable_sig(self.db).expect("first closure param should be fn ptr"); let io = rust_ir::FnDefInputsAndOutputDatum { argument_types: sig.params().to_vec(), @@ -347,13 +365,19 @@ fn closure_fn_substitution( fn trait_name(&self, trait_id: chalk_ir::TraitId) -> String { let id = from_chalk_trait_id(trait_id); - self.db.trait_data(id).name.to_string() + self.db.trait_data(id).name.display(self.db.upcast()).to_string() } fn adt_name(&self, chalk_ir::AdtId(adt_id): AdtId) -> String { match adt_id { - hir_def::AdtId::StructId(id) => self.db.struct_data(id).name.to_string(), - hir_def::AdtId::EnumId(id) => self.db.enum_data(id).name.to_string(), - hir_def::AdtId::UnionId(id) => self.db.union_data(id).name.to_string(), + hir_def::AdtId::StructId(id) => { + self.db.struct_data(id).name.display(self.db.upcast()).to_string() + } + hir_def::AdtId::EnumId(id) => { + self.db.enum_data(id).name.display(self.db.upcast()).to_string() + } + hir_def::AdtId::UnionId(id) => { + self.db.union_data(id).name.display(self.db.upcast()).to_string() + } } } fn adt_size_align(&self, _id: chalk_ir::AdtId) -> Arc { @@ -362,7 +386,7 @@ fn adt_size_align(&self, _id: chalk_ir::AdtId) -> Arc) -> String { let id = self.db.associated_ty_data(assoc_ty_id).name; - self.db.type_alias_data(id).name.to_string() + self.db.type_alias_data(id).name.display(self.db.upcast()).to_string() } fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId) -> String { format!("Opaque_{}", opaque_ty_id.0) @@ -373,7 +397,7 @@ fn fn_def_name(&self, fn_def_id: chalk_ir::FnDefId) -> String { fn generator_datum( &self, id: chalk_ir::GeneratorId, - ) -> std::sync::Arc> { + ) -> Arc> { 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 @@ -398,8 +422,8 @@ fn generator_datum( 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), + hir_def::hir::Expr::Closure { + closure_kind: hir_def::hir::ClosureKind::Generator(movability), .. } => movability, _ => unreachable!("non generator expression interned as generator"), @@ -414,7 +438,7 @@ fn generator_datum( fn generator_witness_datum( &self, id: chalk_ir::GeneratorId, - ) -> std::sync::Arc> { + ) -> Arc> { // FIXME: calculate inner types let inner_types = rust_ir::GeneratorWitnessExistential { types: wrap_empty_binders(vec![]) }; @@ -435,7 +459,7 @@ fn unification_database(&self) -> &dyn chalk_ir::UnificationDatabase { } } -impl<'a> chalk_ir::UnificationDatabase for &'a dyn HirDatabase { +impl chalk_ir::UnificationDatabase for &dyn HirDatabase { fn fn_def_variance( &self, fn_def_id: chalk_ir::FnDefId, @@ -451,9 +475,10 @@ fn adt_variance(&self, adt_id: chalk_ir::AdtId) -> chalk_ir::Variances pub(crate) fn program_clauses_for_chalk_env_query( db: &dyn HirDatabase, krate: CrateId, + block: Option, environment: chalk_ir::Environment, ) -> chalk_ir::ProgramClauses { - chalk_solve::program_clauses_for_env(&ChalkContext { db, krate }, &environment) + chalk_solve::program_clauses_for_env(&ChalkContext { db, krate, block }, &environment) } pub(crate) fn associated_ty_data_query( @@ -786,17 +811,17 @@ pub(crate) fn adt_variance_query( ) } +/// Returns instantiated predicates. pub(super) fn convert_where_clauses( db: &dyn HirDatabase, def: GenericDefId, substs: &Substitution, ) -> Vec> { - let generic_predicates = db.generic_predicates(def); - let mut result = Vec::with_capacity(generic_predicates.len()); - for pred in generic_predicates.iter() { - result.push(pred.clone().substitute(Interner, substs)); - } - result + db.generic_predicates(def) + .iter() + .cloned() + .map(|pred| pred.substitute(Interner, substs)) + .collect() } pub(super) fn generic_predicate_to_inline_bound( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs index 2141894922f7..a8071591adac 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs @@ -1,24 +1,28 @@ //! Various extensions traits for Chalk types. -use chalk_ir::{FloatTy, IntTy, Mutability, Scalar, TyVariableKind, UintTy}; +use chalk_ir::{cast::Cast, FloatTy, IntTy, Mutability, Scalar, TyVariableKind, UintTy}; use hir_def::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType, BuiltinUint}, generics::TypeOrConstParamData, lang_item::LangItem, type_ref::Rawness, - FunctionId, GenericDefId, HasModule, ItemContainerId, Lookup, TraitId, + DefWithBodyId, FunctionId, GenericDefId, HasModule, ItemContainerId, Lookup, TraitId, }; use crate::{ - db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, - from_placeholder_idx, to_chalk_trait_id, utils::generics, AdtId, AliasEq, AliasTy, Binders, - CallableDefId, CallableSig, DynTy, FnPointer, ImplTraitId, Interner, Lifetime, ProjectionTy, + db::HirDatabase, + from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, + to_chalk_trait_id, + utils::{generics, ClosureSubst}, + AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, + ClosureId, DynTy, FnPointer, ImplTraitId, InEnvironment, Interner, Lifetime, ProjectionTy, QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, TypeFlags, WhereClause, }; pub trait TyExt { fn is_unit(&self) -> bool; fn is_integral(&self) -> bool; + fn is_scalar(&self) -> bool; fn is_floating_point(&self) -> bool; fn is_never(&self) -> bool; fn is_unknown(&self) -> bool; @@ -28,8 +32,10 @@ pub trait TyExt { fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>; fn as_builtin(&self) -> Option; fn as_tuple(&self) -> Option<&Substitution>; + fn as_closure(&self) -> Option; fn as_fn_def(&self, db: &dyn HirDatabase) -> Option; fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>; + fn as_raw_ptr(&self) -> Option<(&Ty, Mutability)>; fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)>; fn as_generic_def(&self, db: &dyn HirDatabase) -> Option; @@ -44,6 +50,7 @@ pub trait TyExt { fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option>; fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option; + fn is_copy(self, db: &dyn HirDatabase, owner: DefWithBodyId) -> bool; /// FIXME: Get rid of this, it's not a good abstraction fn equals_ctor(&self, other: &Ty) -> bool; @@ -62,6 +69,10 @@ fn is_integral(&self) -> bool { ) } + fn is_scalar(&self) -> bool { + matches!(self.kind(Interner), TyKind::Scalar(_)) + } + fn is_floating_point(&self) -> bool { matches!( self.kind(Interner), @@ -128,12 +139,20 @@ fn as_tuple(&self) -> Option<&Substitution> { } } + fn as_closure(&self) -> Option { + match self.kind(Interner) { + TyKind::Closure(id, _) => Some(*id), + _ => None, + } + } + fn as_fn_def(&self, db: &dyn HirDatabase) -> Option { match self.callable_def(db) { Some(CallableDefId::FunctionId(func)) => Some(func), Some(CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_)) | None => None, } } + fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)> { match self.kind(Interner) { TyKind::Ref(mutability, lifetime, ty) => Some((ty, lifetime.clone(), *mutability)), @@ -141,6 +160,13 @@ fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)> { } } + fn as_raw_ptr(&self) -> Option<(&Ty, Mutability)> { + match self.kind(Interner) { + TyKind::Raw(mutability, ty) => Some((ty, *mutability)), + _ => None, + } + } + fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)> { match self.kind(Interner) { TyKind::Ref(mutability, _, ty) => Some((ty, Rawness::Ref, *mutability)), @@ -176,10 +202,7 @@ fn callable_sig(&self, db: &dyn HirDatabase) -> Option { let sig = db.callable_item_signature(callable_def); Some(sig.substitute(Interner, parameters)) } - TyKind::Closure(.., substs) => { - let sig_param = substs.at(Interner, 0).assert_ty_ref(Interner); - sig_param.callable_sig(db) - } + TyKind::Closure(.., substs) => ClosureSubst(substs).sig_ty().callable_sig(db), _ => None, } } @@ -318,6 +341,20 @@ fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option } } + fn is_copy(self, db: &dyn HirDatabase, owner: DefWithBodyId) -> bool { + let crate_id = owner.module(db.upcast()).krate(); + let Some(copy_trait) = db.lang_item(crate_id, LangItem::Copy).and_then(|x| x.as_trait()) else { + return false; + }; + let trait_ref = TyBuilder::trait_ref(db, copy_trait).push(self).build(); + let env = db.trait_environment_for_body(owner); + let goal = Canonical { + value: InEnvironment::new(&env.env, trait_ref.cast(Interner)), + binders: CanonicalVarKinds::empty(Interner), + }; + db.trait_solve(crate_id, None, goal).is_some() + } + fn equals_ctor(&self, other: &Ty) -> bool { match (self.kind(Interner), other.kind(Interner)) { (TyKind::Adt(adt, ..), TyKind::Adt(adt2, ..)) => adt == adt2, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs index 5830c48988fe..40b63b17b5aa 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -3,19 +3,20 @@ use base_db::CrateId; use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData}; use hir_def::{ - expr::Expr, - path::ModPath, + hir::Expr, + path::Path, resolver::{Resolver, ValueNs}, type_ref::ConstRef, - ConstId, EnumVariantId, + EnumVariantId, GeneralConstId, StaticId, }; use la_arena::{Idx, RawIdx}; use stdx::never; +use triomphe::Arc; use crate::{ - db::HirDatabase, infer::InferenceContext, layout::layout_of_ty, lower::ParamLoweringMode, - to_placeholder_idx, utils::Generics, Const, ConstData, ConstScalar, ConstValue, GenericArg, - Interner, MemoryMap, Ty, TyBuilder, + db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, + mir::monomorphize_mir_body_bad, to_placeholder_idx, utils::Generics, Const, ConstData, + ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution, Ty, TyBuilder, }; use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError}; @@ -57,7 +58,7 @@ pub enum ConstEvalError { impl From for ConstEvalError { fn from(value: MirLowerError) -> Self { match value { - MirLowerError::ConstEvalError(e) => *e, + MirLowerError::ConstEvalError(_, e) => *e, _ => ConstEvalError::MirLowerError(value), } } @@ -72,10 +73,11 @@ fn from(value: MirEvalError) -> Self { pub(crate) fn path_to_const( db: &dyn HirDatabase, resolver: &Resolver, - path: &ModPath, + path: &Path, mode: ParamLoweringMode, args_lazy: impl FnOnce() -> Generics, debruijn: DebruijnIndex, + expected_ty: Ty, ) -> Option { match resolver.resolve_path_in_value_ns_fully(db.upcast(), path) { Some(ValueNs::GenericParam(p)) => { @@ -89,7 +91,7 @@ pub(crate) fn path_to_const( Some(x) => ConstValue::BoundVar(BoundVar::new(debruijn, x)), None => { never!( - "Generic list doesn't contain this param: {:?}, {}, {:?}", + "Generic list doesn't contain this param: {:?}, {:?}, {:?}", args, path, p @@ -100,6 +102,10 @@ pub(crate) fn path_to_const( }; Some(ConstData { ty, value }.intern(Interner)) } + Some(ValueNs::ConstId(c)) => Some(intern_const_scalar( + ConstScalar::UnevaluatedConst(c.into(), Substitution::empty(Interner)), + expected_ty, + )), _ => None, } } @@ -124,14 +130,15 @@ pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const { /// Interns a constant scalar with the given type pub fn intern_const_ref(db: &dyn HirDatabase, value: &ConstRef, ty: Ty, krate: CrateId) -> Const { + let layout = db.layout_of_ty(ty.clone(), krate); let bytes = match value { ConstRef::Int(i) => { // FIXME: We should handle failure of layout better. - let size = layout_of_ty(db, &ty, krate).map(|x| x.size.bytes_usize()).unwrap_or(16); + let size = layout.map(|x| x.size.bytes_usize()).unwrap_or(16); ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default()) } ConstRef::UInt(i) => { - let size = layout_of_ty(db, &ty, krate).map(|x| x.size.bytes_usize()).unwrap_or(16); + let size = layout.map(|x| x.size.bytes_usize()).unwrap_or(16); ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default()) } ConstRef::Bool(b) => ConstScalar::Bytes(vec![*b as u8], MemoryMap::default()), @@ -153,13 +160,17 @@ pub fn usize_const(db: &dyn HirDatabase, value: Option, krate: CrateId) -> ) } -pub fn try_const_usize(c: &Const) -> Option { +pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option { match &c.data(Interner).value { chalk_ir::ConstValue::BoundVar(_) => None, chalk_ir::ConstValue::InferenceVar(_) => None, chalk_ir::ConstValue::Placeholder(_) => None, chalk_ir::ConstValue::Concrete(c) => match &c.interned { ConstScalar::Bytes(x, _) => Some(u128::from_le_bytes(pad16(&x, false))), + ConstScalar::UnevaluatedConst(c, subst) => { + let ec = db.const_eval(*c, subst.clone()).ok()?; + try_const_usize(db, &ec) + } _ => None, }, } @@ -168,7 +179,16 @@ pub fn try_const_usize(c: &Const) -> Option { pub(crate) fn const_eval_recover( _: &dyn HirDatabase, _: &[String], - _: &ConstId, + _: &GeneralConstId, + _: &Substitution, +) -> Result { + Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) +} + +pub(crate) fn const_eval_static_recover( + _: &dyn HirDatabase, + _: &[String], + _: &StaticId, ) -> Result { Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } @@ -183,11 +203,39 @@ pub(crate) fn const_eval_discriminant_recover( pub(crate) fn const_eval_query( db: &dyn HirDatabase, - const_id: ConstId, + def: GeneralConstId, + subst: Substitution, ) -> Result { - let def = const_id.into(); - let body = db.mir_body(def)?; - let c = interpret_mir(db, &body, false)?; + let body = match def { + GeneralConstId::ConstId(c) => { + db.monomorphized_mir_body(c.into(), subst, db.trait_environment(c.into()))? + } + GeneralConstId::AnonymousConstId(c) => { + let (def, root) = db.lookup_intern_anonymous_const(c); + let body = db.body(def); + let infer = db.infer(def); + Arc::new(monomorphize_mir_body_bad( + db, + lower_to_mir(db, def, &body, &infer, root)?, + subst, + db.trait_environment_for_body(def), + )?) + } + }; + let c = interpret_mir(db, &body, false).0?; + Ok(c) +} + +pub(crate) fn const_eval_static_query( + db: &dyn HirDatabase, + def: StaticId, +) -> Result { + let body = db.monomorphized_mir_body( + def.into(), + Substitution::empty(Interner), + db.trait_environment_for_body(def.into()), + )?; + let c = interpret_mir(db, &body, false).0?; Ok(c) } @@ -209,9 +257,13 @@ pub(crate) fn const_eval_discriminant_variant( }; return Ok(value); } - let mir_body = db.mir_body(def)?; - let c = interpret_mir(db, &mir_body, false)?; - let c = try_const_usize(&c).unwrap() as i128; + let mir_body = db.monomorphized_mir_body( + def, + Substitution::empty(Interner), + db.trait_environment_for_body(def), + )?; + let c = interpret_mir(db, &mir_body, false).0?; + let c = try_const_usize(db, &c).unwrap() as i128; Ok(c) } @@ -226,15 +278,16 @@ pub(crate) fn eval_to_const( debruijn: DebruijnIndex, ) -> Const { let db = ctx.db; + let infer = ctx.clone().resolve_all(); if let Expr::Path(p) = &ctx.body.exprs[expr] { let resolver = &ctx.resolver; - if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) { + if let Some(c) = path_to_const(db, resolver, p, mode, args, debruijn, infer[expr].clone()) { return c; } } let infer = ctx.clone().resolve_all(); if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) { - if let Ok(result) = interpret_mir(db, &mir_body, true) { + if let Ok(result) = interpret_mir(db, &mir_body, true).0 { return result; } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs index 6a29e8ce52e6..06fff08b7d35 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs @@ -1,8 +1,10 @@ -use base_db::fixture::WithFixture; +use base_db::{fixture::WithFixture, FileId}; +use chalk_ir::Substitution; use hir_def::db::DefDatabase; use crate::{ - consteval::try_const_usize, db::HirDatabase, test_db::TestDB, Const, ConstScalar, Interner, + consteval::try_const_usize, db::HirDatabase, mir::pad16, test_db::TestDB, Const, ConstScalar, + Interner, }; use super::{ @@ -10,9 +12,11 @@ ConstEvalError, }; +mod intrinsics; + fn simplify(e: ConstEvalError) -> ConstEvalError { match e { - ConstEvalError::MirEvalError(MirEvalError::InFunction(_, e)) => { + ConstEvalError::MirEvalError(MirEvalError::InFunction(_, e, _, _)) => { simplify(ConstEvalError::MirEvalError(*e)) } _ => e, @@ -20,17 +24,35 @@ fn simplify(e: ConstEvalError) -> ConstEvalError { } #[track_caller] -fn check_fail(ra_fixture: &str, error: ConstEvalError) { - assert_eq!(eval_goal(ra_fixture).map_err(simplify), Err(error)); +fn check_fail(ra_fixture: &str, error: impl FnOnce(ConstEvalError) -> bool) { + let (db, file_id) = TestDB::with_single_file(ra_fixture); + match eval_goal(&db, file_id) { + Ok(_) => panic!("Expected fail, but it succeeded"), + Err(e) => { + assert!(error(simplify(e.clone())), "Actual error was: {}", pretty_print_err(e, db)) + } + } } #[track_caller] fn check_number(ra_fixture: &str, answer: i128) { - let r = eval_goal(ra_fixture).unwrap(); + let (db, file_id) = TestDB::with_single_file(ra_fixture); + let r = match eval_goal(&db, file_id) { + Ok(t) => t, + Err(e) => { + let err = pretty_print_err(e, db); + panic!("Error in evaluating goal: {}", err); + } + }; match &r.data(Interner).value { chalk_ir::ConstValue::Concrete(c) => match &c.interned { ConstScalar::Bytes(b, _) => { - assert_eq!(b, &answer.to_le_bytes()[0..b.len()]); + assert_eq!( + b, + &answer.to_le_bytes()[0..b.len()], + "Bytes differ. In decimal form: actual = {}, expected = {answer}", + i128::from_le_bytes(pad16(b, true)) + ); } x => panic!("Expected number but found {:?}", x), }, @@ -38,16 +60,26 @@ fn check_number(ra_fixture: &str, answer: i128) { } } -fn eval_goal(ra_fixture: &str) -> Result { - let (db, file_id) = TestDB::with_single_file(ra_fixture); +fn pretty_print_err(e: ConstEvalError, db: TestDB) -> String { + let mut err = String::new(); + let span_formatter = |file, range| format!("{:?} {:?}", file, range); + match e { + ConstEvalError::MirLowerError(e) => e.pretty_print(&mut err, &db, span_formatter), + ConstEvalError::MirEvalError(e) => e.pretty_print(&mut err, &db, span_formatter), + } + .unwrap(); + err +} + +fn eval_goal(db: &TestDB, file_id: FileId) -> Result { let module_id = db.module_for_file(file_id); - let def_map = module_id.def_map(&db); + let def_map = module_id.def_map(db); let scope = &def_map[module_id.local_id].scope; let const_id = scope .declarations() .find_map(|x| match x { hir_def::ModuleDefId::ConstId(x) => { - if db.const_data(x).name.as_ref()?.to_string() == "GOAL" { + if db.const_data(x).name.as_ref()?.display(db).to_string() == "GOAL" { Some(x) } else { None @@ -56,7 +88,7 @@ fn eval_goal(ra_fixture: &str) -> Result { _ => None, }) .unwrap(); - db.const_eval(const_id) + db.const_eval(const_id.into(), Substitution::empty(Interner)) } #[test] @@ -72,8 +104,98 @@ fn bit_op() { check_number(r#"const GOAL: u8 = !0 & !(!0 >> 1)"#, 128); check_number(r#"const GOAL: i8 = !0 & !(!0 >> 1)"#, 0); check_number(r#"const GOAL: i8 = 1 << 7"#, (1i8 << 7) as i128); - // FIXME: report panic here - check_number(r#"const GOAL: i8 = 1 << 8"#, 0); + check_number(r#"const GOAL: i8 = -1 << 2"#, (-1i8 << 2) as i128); + check_fail(r#"const GOAL: i8 = 1 << 8"#, |e| { + e == ConstEvalError::MirEvalError(MirEvalError::Panic("Overflow in Shl".to_string())) + }); +} + +#[test] +fn floating_point() { + check_number( + r#"const GOAL: f64 = 2.0 + 3.0 * 5.5 - 8.;"#, + i128::from_le_bytes(pad16(&f64::to_le_bytes(10.5), true)), + ); + check_number( + r#"const GOAL: f32 = 2.0 + 3.0 * 5.5 - 8.;"#, + i128::from_le_bytes(pad16(&f32::to_le_bytes(10.5), true)), + ); + check_number( + r#"const GOAL: f32 = -90.0 + 36.0;"#, + i128::from_le_bytes(pad16(&f32::to_le_bytes(-54.0), true)), + ); +} + +#[test] +fn casts() { + check_number(r#"const GOAL: usize = 12 as *const i32 as usize"#, 12); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: i32 = { + let a = [10, 20, 3, 15]; + let x: &[i32] = &a; + let y: *const [i32] = x; + let z = y as *const i32; + unsafe { *z } + }; + "#, + 10, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: i16 = { + let a = &mut 5; + let z = a as *mut _; + unsafe { *z } + }; + "#, + 5, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: usize = { + let a = &[10, 20, 30, 40] as &[i32]; + a.len() + }; + "#, + 4, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: usize = { + let a = [10, 20, 3, 15]; + let x: &[i32] = &a; + let y: *const [i32] = x; + let z = y as *const [u8]; // slice fat pointer cast don't touch metadata + let q = z as *const str; + let p = q as *const [u8]; + let w = unsafe { &*z }; + w.len() + }; + "#, + 4, + ); + check_number(r#"const GOAL: i32 = -12i8 as i32"#, -12); +} + +#[test] +fn raw_pointer_equality() { + check_number( + r#" + //- minicore: copy, eq + const GOAL: bool = { + let a = 2; + let p1 = a as *const i32; + let p2 = a as *const i32; + p1 == p2 + }; + "#, + 1, + ); } #[test] @@ -166,8 +288,7 @@ fn g(i: Foo>) -> i32 { #[test] fn overloaded_deref() { - // FIXME: We should support this. - check_fail( + check_number( r#" //- minicore: deref_mut struct Foo; @@ -185,9 +306,7 @@ fn deref(&self) -> &i32 { *y + *x }; "#, - ConstEvalError::MirLowerError(MirLowerError::NotSupported( - "explicit overloaded deref".into(), - )), + 10, ); } @@ -218,6 +337,117 @@ fn method(&self) -> i32 { ); } +#[test] +fn overloaded_index() { + check_number( + r#" + //- minicore: index + struct Foo; + + impl core::ops::Index for Foo { + type Output = i32; + fn index(&self, index: usize) -> &i32 { + if index == 7 { + &700 + } else { + &1000 + } + } + } + + impl core::ops::IndexMut for Foo { + fn index_mut(&mut self, index: usize) -> &mut i32 { + if index == 7 { + &mut 7 + } else { + &mut 10 + } + } + } + + const GOAL: i32 = { + (Foo[2]) + (Foo[7]) + (*&Foo[2]) + (*&Foo[7]) + (*&mut Foo[2]) + (*&mut Foo[7]) + }; + "#, + 3417, + ); +} + +#[test] +fn overloaded_binop() { + check_number( + r#" + //- minicore: add + enum Color { + Red, + Green, + Yellow, + } + + use Color::*; + + impl core::ops::Add for Color { + type Output = Color; + fn add(self, rhs: Color) -> Self::Output { + Yellow + } + } + + impl core::ops::AddAssign for Color { + fn add_assign(&mut self, rhs: Color) { + *self = Red; + } + } + + const GOAL: bool = { + let x = Red + Green; + let mut y = Green; + y += x; + x == Yellow && y == Red && Red + Green == Yellow && Red + Red == Yellow && Yellow + Green == Yellow + }; + "#, + 1, + ); + check_number( + r#" + //- minicore: add + impl core::ops::Add for usize { + type Output = usize; + fn add(self, rhs: usize) -> Self::Output { + self + rhs + } + } + + impl core::ops::AddAssign for usize { + fn add_assign(&mut self, rhs: usize) { + *self += rhs; + } + } + + #[lang = "shl"] + pub trait Shl { + type Output; + + fn shl(self, rhs: Rhs) -> Self::Output; + } + + impl Shl for usize { + type Output = usize; + + fn shl(self, rhs: u8) -> Self::Output { + self << rhs + } + } + + const GOAL: usize = { + let mut x = 10; + x += 20; + 2 + 2 + (x << 1u8) + };"#, + 64, + ); +} + #[test] fn function_call() { check_number( @@ -240,20 +470,6 @@ const fn add(x: usize, y: usize) -> usize { ); } -#[test] -fn intrinsics() { - check_number( - r#" - extern "rust-intrinsic" { - pub fn size_of() -> usize; - } - - const GOAL: usize = size_of::(); - "#, - 4, - ); -} - #[test] fn trait_basic() { check_number( @@ -300,6 +516,35 @@ fn f(&self) -> u8 { ); } +#[test] +fn trait_method_inside_block() { + check_number( + r#" +trait Twait { + fn a(&self) -> i32; +} + +fn outer() -> impl Twait { + struct Stwuct; + + impl Twait for Stwuct { + fn a(&self) -> i32 { + 5 + } + } + fn f() -> impl Twait { + let s = Stwuct; + s + } + f() +} + +const GOAL: i32 = outer().a(); + "#, + 5, + ); +} + #[test] fn generic_fn() { check_number( @@ -355,6 +600,16 @@ fn bar(a: A, b: B) -> B { "#, 12, ); + check_number( + r#" + const fn y(b: T) -> (T, ) { + let alloc = b; + (alloc, ) + } + const GOAL: u8 = y(2).0; + "#, + 2, + ); check_number( r#" //- minicore: coerce_unsized, index, slice @@ -483,6 +738,66 @@ fn loops() { "#, 4, ); + check_number( + r#" + const GOAL: u8 = { + let mut x = 0; + loop { + x = x + 1; + if x == 5 { + break x + 2; + } + } + }; + "#, + 7, + ); + check_number( + r#" + const GOAL: u8 = { + 'a: loop { + let x = 'b: loop { + let x = 'c: loop { + let x = 'd: loop { + let x = 'e: loop { + break 'd 1; + }; + break 2 + x; + }; + break 3 + x; + }; + break 'a 4 + x; + }; + break 5 + x; + } + }; + "#, + 8, + ); + check_number( + r#" + //- minicore: add + const GOAL: u8 = { + let mut x = 0; + 'a: loop { + 'b: loop { + 'c: while x < 20 { + 'd: while x < 5 { + 'e: loop { + x += 1; + continue 'c; + }; + }; + x += 1; + }; + break 'a; + }; + } + x + }; + "#, + 20, + ); } #[test] @@ -522,6 +837,18 @@ fn next(&mut self) -> Option { ); } +#[test] +fn ranges() { + check_number( + r#" + //- minicore: range + const GOAL: i32 = (1..2).start + (20..10).end + (100..=200).start + (2000..=1000).end + + (10000..).start + (..100000).end + (..=1000000).end; + "#, + 1111111, + ); +} + #[test] fn recursion() { check_number( @@ -555,6 +882,38 @@ struct Point { "#, 17, ); + check_number( + r#" + struct Point { + x: i32, + y: i32, + } + + const GOAL: i32 = { + let p = Point { x: 5, y: 2 }; + let p2 = Point { x: 3, ..p }; + p.x * 1000 + p.y * 100 + p2.x * 10 + p2.y + }; + "#, + 5232, + ); + check_number( + r#" + struct Point { + x: i32, + y: i32, + } + + const GOAL: i32 = { + let p = Point { x: 5, y: 2 }; + let Point { x, y } = p; + let Point { x: x2, .. } = p; + let Point { y: y2, .. } = p; + x * 1000 + y * 100 + x2 * 10 + y2 + }; + "#, + 5252, + ); } #[test] @@ -599,13 +958,14 @@ fn tuples() { ); check_number( r#" - struct TupleLike(i32, u8, i64, u16); - const GOAL: u8 = { + struct TupleLike(i32, i64, u8, u16); + const GOAL: i64 = { let a = TupleLike(10, 20, 3, 15); - a.1 + let TupleLike(b, .., c) = a; + a.1 * 100 + b as i64 + c as i64 }; "#, - 20, + 2025, ); check_number( r#" @@ -638,11 +998,17 @@ enum Season { use Season::*; + const MY_SEASON: Season = Summer; + + impl Season { + const FALL: Season = Fall; + } + const fn f(x: Season) -> i32 { match x { Spring => 1, - Summer => 2, - Fall => 3, + MY_SEASON => 2, + Season::FALL => 3, Winter => 4, } } @@ -652,6 +1018,91 @@ const fn f(x: Season) -> i32 { ); } +#[test] +fn pattern_matching_literal() { + check_number( + r#" + const fn f(x: i32) -> i32 { + match x { + -1 => 1, + 1 => 10, + _ => 100, + } + } + const GOAL: i32 = f(-1) + f(1) + f(0) + f(-5); + "#, + 211, + ); + check_number( + r#" + const fn f(x: &str) -> i32 { + match x { + "f" => 1, + "foo" => 10, + "" => 100, + "bar" => 1000, + _ => 10000, + } + } + const GOAL: i32 = f("f") + f("foo") * 2 + f("") * 3 + f("bar") * 4; + "#, + 4321, + ); +} + +#[test] +fn pattern_matching_range() { + check_number( + r#" + pub const L: i32 = 6; + mod x { + pub const R: i32 = 100; + } + const fn f(x: i32) -> i32 { + match x { + -1..=5 => x * 10, + L..=x::R => x * 100, + _ => x, + } + } + const GOAL: i32 = f(-1) + f(2) + f(100) + f(-2) + f(1000); + "#, + 11008, + ); +} + +#[test] +fn pattern_matching_slice() { + check_number( + r#" + //- minicore: slice, index, coerce_unsized, copy + const fn f(x: &[usize]) -> usize { + match x { + [a, b @ .., c, d] => *a + b.len() + *c + *d, + } + } + const GOAL: usize = f(&[10, 20, 3, 15, 1000, 60, 16]); + "#, + 10 + 4 + 60 + 16, + ); + check_number( + r#" + //- minicore: slice, index, coerce_unsized, copy + const fn f(x: &[usize]) -> usize { + match x { + [] => 0, + [a] => *a, + &[a, b] => a + b, + [a, b @ .., c, d] => *a + b.len() + *c + *d, + } + } + const GOAL: usize = f(&[]) + f(&[10]) + f(&[100, 100]) + + f(&[1000, 1000, 1000]) + f(&[10000, 57, 34, 46, 10000, 10000]); + "#, + 33213, + ); +} + #[test] fn pattern_matching_ergonomics() { check_number( @@ -665,6 +1116,16 @@ const fn f(x: &(u8, u8)) -> u8 { "#, 5, ); + check_number( + r#" + const GOAL: u8 = { + let a = &(2, 3); + let &(x, y) = a; + x + y + }; + "#, + 5, + ); } #[test] @@ -748,6 +1209,77 @@ const fn f(&self, (a, b): &(u8, u8)) -> u8 { ); } +#[test] +fn match_guards() { + check_number( + r#" + //- minicore: option + fn f(x: Option) -> i32 { + match x { + y if let Some(42) = y => 42000, + Some(y) => y, + None => 10 + } + } + const GOAL: i32 = f(Some(42)) + f(Some(2)) + f(None); + "#, + 42012, + ); +} + +#[test] +fn result_layout_niche_optimization() { + check_number( + r#" + //- minicore: option, result + const GOAL: i32 = match Some(2).ok_or(Some(2)) { + Ok(x) => x, + Err(_) => 1000, + }; + "#, + 2, + ); + check_number( + r#" + //- minicore: result + pub enum AlignmentEnum64 { + _Align1Shl0 = 1 << 0, + _Align1Shl1 = 1 << 1, + _Align1Shl2 = 1 << 2, + _Align1Shl3 = 1 << 3, + _Align1Shl4 = 1 << 4, + _Align1Shl5 = 1 << 5, + } + const GOAL: Result = { + let align = Err(()); + align + }; + "#, + 0, // It is 0 since result is niche encoded and 1 is valid for `AlignmentEnum64` + ); + check_number( + r#" + //- minicore: result + pub enum AlignmentEnum64 { + _Align1Shl0 = 1 << 0, + _Align1Shl1 = 1 << 1, + _Align1Shl2 = 1 << 2, + _Align1Shl3 = 1 << 3, + _Align1Shl4 = 1 << 4, + _Align1Shl5 = 1 << 5, + } + const GOAL: i32 = { + let align = Ok::<_, ()>(AlignmentEnum64::_Align1Shl0); + match align { + Ok(_) => 2, + Err(_) => 1, + } + }; + "#, + 2, + ); +} + #[test] fn options() { check_number( @@ -801,6 +1333,253 @@ fn f(x: Option>) -> i32 { ); } +#[test] +fn from_trait() { + check_number( + r#" + //- minicore: from + struct E1(i32); + struct E2(i32); + + impl From for E2 { + fn from(E1(x): E1) -> Self { + E2(1000 * x) + } + } + const GOAL: i32 = { + let x: E2 = E1(2).into(); + x.0 + }; + "#, + 2000, + ); +} + +#[test] +fn builtin_derive_macro() { + check_number( + r#" + //- minicore: clone, derive, builtin_impls + #[derive(Clone)] + enum Z { + Foo(Y), + Bar, + } + #[derive(Clone)] + struct X(i32, Z, i64) + #[derive(Clone)] + struct Y { + field1: i32, + field2: u8, + } + + const GOAL: u8 = { + let x = X(2, Z::Foo(Y { field1: 4, field2: 5 }), 8); + let x = x.clone(); + let Z::Foo(t) = x.1; + t.field2 + }; + "#, + 5, + ); + check_number( + r#" + //- minicore: default, derive, builtin_impls + #[derive(Default)] + struct X(i32, Y, i64) + #[derive(Default)] + struct Y { + field1: i32, + field2: u8, + } + + const GOAL: u8 = { + let x = X::default(); + x.1.field2 + }; + "#, + 0, + ); +} + +#[test] +fn try_operator() { + check_number( + r#" + //- minicore: option, try + const fn f(x: Option, y: Option) -> Option { + Some(x? * y?) + } + const fn g(x: Option, y: Option) -> i32 { + match f(x, y) { + Some(k) => k, + None => 5, + } + } + const GOAL: i32 = g(Some(10), Some(20)) + g(Some(30), None) + g(None, Some(40)) + g(None, None); + "#, + 215, + ); + check_number( + r#" + //- minicore: result, try, from + struct E1(i32); + struct E2(i32); + + impl From for E2 { + fn from(E1(x): E1) -> Self { + E2(1000 * x) + } + } + + const fn f(x: Result) -> Result { + Ok(x? * 10) + } + const fn g(x: Result) -> i32 { + match f(x) { + Ok(k) => 7 * k, + Err(E2(k)) => 5 * k, + } + } + const GOAL: i32 = g(Ok(2)) + g(Err(E1(3))); + "#, + 15140, + ); +} + +#[test] +fn try_block() { + check_number( + r#" + //- minicore: option, try + const fn g(x: Option, y: Option) -> i32 { + let r = try { x? * y? }; + match r { + Some(k) => k, + None => 5, + } + } + const GOAL: i32 = g(Some(10), Some(20)) + g(Some(30), None) + g(None, Some(40)) + g(None, None); + "#, + 215, + ); +} + +#[test] +fn closures() { + check_number( + r#" + //- minicore: fn, copy + const GOAL: i32 = { + let y = 5; + let c = |x| x + y; + c(2) + }; + "#, + 7, + ); + check_number( + r#" + //- minicore: fn, copy + const GOAL: i32 = { + let y = 5; + let c = |(a, b): &(i32, i32)| *a + *b + y; + c(&(2, 3)) + }; + "#, + 10, + ); + check_number( + r#" + //- minicore: fn, copy + const GOAL: i32 = { + let mut y = 5; + let c = |x| { + y = y + x; + }; + c(2); + c(3); + y + }; + "#, + 10, + ); + check_number( + r#" + //- minicore: fn, copy + const GOAL: i32 = { + let c: fn(i32) -> i32 = |x| 2 * x; + c(2) + c(10) + }; + "#, + 24, + ); + check_number( + r#" + //- minicore: fn, copy + struct X(i32); + impl X { + fn mult(&mut self, n: i32) { + self.0 = self.0 * n + } + } + const GOAL: i32 = { + let x = X(1); + let c = || { + x.mult(2); + || { + x.mult(3); + || { + || { + x.mult(4); + || { + x.mult(x.0); + || { + x.0 + } + } + } + } + } + }; + let r = c()()()()()(); + r + x.0 + }; + "#, + 24 * 24 * 2, + ); +} + +#[test] +fn closure_and_impl_fn() { + check_number( + r#" + //- minicore: fn, copy + fn closure_wrapper i32>(c: F) -> impl FnOnce() -> F { + || c + } + + const GOAL: i32 = { + let y = 5; + let c = closure_wrapper(|| y); + c()() + }; + "#, + 5, + ); + check_number( + r#" + //- minicore: fn, copy + fn f T>(t: F) -> impl Fn() -> T { + move || t() + } + + const GOAL: i32 = f(|| 2)(); + "#, + 2, + ); +} + #[test] fn or_pattern() { check_number( @@ -839,6 +1618,282 @@ const fn f(x: Option, y: Option) -> i32 { ); } +#[test] +fn function_pointer_in_constants() { + check_number( + r#" + struct Foo { + f: fn(u8) -> u8, + } + const FOO: Foo = Foo { f: add2 }; + fn add2(x: u8) -> u8 { + x + 2 + } + const GOAL: u8 = (FOO.f)(3); + "#, + 5, + ); +} + +#[test] +fn function_pointer() { + check_number( + r#" + fn add2(x: u8) -> u8 { + x + 2 + } + const GOAL: u8 = { + let plus2 = add2; + plus2(3) + }; + "#, + 5, + ); + check_number( + r#" + fn add2(x: u8) -> u8 { + x + 2 + } + const GOAL: u8 = { + let plus2: fn(u8) -> u8 = add2; + plus2(3) + }; + "#, + 5, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + fn add2(x: u8) -> u8 { + x + 2 + } + fn mult3(x: u8) -> u8 { + x * 3 + } + const GOAL: u8 = { + let x = [add2, mult3]; + x[0](1) + x[1](5) + }; + "#, + 18, + ); +} + +#[test] +fn enum_variant_as_function() { + check_number( + r#" + //- minicore: option + const GOAL: u8 = { + let f = Some; + f(3).unwrap_or(2) + }; + "#, + 3, + ); + check_number( + r#" + //- minicore: option + const GOAL: u8 = { + let f: fn(u8) -> Option = Some; + f(3).unwrap_or(2) + }; + "#, + 3, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + enum Foo { + Add2(u8), + Mult3(u8), + } + use Foo::*; + const fn f(x: Foo) -> u8 { + match x { + Add2(x) => x + 2, + Mult3(x) => x * 3, + } + } + const GOAL: u8 = { + let x = [Add2, Mult3]; + f(x[0](1)) + f(x[1](5)) + }; + "#, + 18, + ); +} + +#[test] +fn function_traits() { + check_number( + r#" + //- minicore: fn + fn add2(x: u8) -> u8 { + x + 2 + } + fn call(f: impl Fn(u8) -> u8, x: u8) -> u8 { + f(x) + } + fn call_mut(mut f: impl FnMut(u8) -> u8, x: u8) -> u8 { + f(x) + } + fn call_once(f: impl FnOnce(u8) -> u8, x: u8) -> u8 { + f(x) + } + const GOAL: u8 = call(add2, 3) + call_mut(add2, 3) + call_once(add2, 3); + "#, + 15, + ); + check_number( + r#" + //- minicore: coerce_unsized, fn + fn add2(x: u8) -> u8 { + x + 2 + } + fn call(f: &dyn Fn(u8) -> u8, x: u8) -> u8 { + f(x) + } + fn call_mut(f: &mut dyn FnMut(u8) -> u8, x: u8) -> u8 { + f(x) + } + const GOAL: u8 = call(&add2, 3) + call_mut(&mut add2, 3); + "#, + 10, + ); + check_number( + r#" + //- minicore: fn + fn add2(x: u8) -> u8 { + x + 2 + } + fn call(f: impl Fn(u8) -> u8, x: u8) -> u8 { + f(x) + } + fn call_mut(mut f: impl FnMut(u8) -> u8, x: u8) -> u8 { + f(x) + } + fn call_once(f: impl FnOnce(u8) -> u8, x: u8) -> u8 { + f(x) + } + const GOAL: u8 = { + let add2: fn(u8) -> u8 = add2; + call(add2, 3) + call_mut(add2, 3) + call_once(add2, 3) + }; + "#, + 15, + ); + check_number( + r#" + //- minicore: fn + fn add2(x: u8) -> u8 { + x + 2 + } + fn call(f: &&&&&impl Fn(u8) -> u8, x: u8) -> u8 { + f(x) + } + const GOAL: u8 = call(&&&&&add2, 3); + "#, + 5, + ); +} + +#[test] +fn dyn_trait() { + check_number( + r#" + //- minicore: coerce_unsized, index, slice + trait Foo { + fn foo(&self) -> u8 { 10 } + } + struct S1; + struct S2; + struct S3; + impl Foo for S1 { + fn foo(&self) -> u8 { 1 } + } + impl Foo for S2 { + fn foo(&self) -> u8 { 2 } + } + impl Foo for S3 {} + const GOAL: u8 = { + let x: &[&dyn Foo] = &[&S1, &S2, &S3]; + x[0].foo() + x[1].foo() + x[2].foo() + }; + "#, + 13, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + trait Foo { + fn foo(&self) -> i32 { 10 } + } + trait Bar { + fn bar(&self) -> i32 { 20 } + } + + struct S; + impl Foo for S { + fn foo(&self) -> i32 { 200 } + } + impl Bar for dyn Foo { + fn bar(&self) -> i32 { 700 } + } + const GOAL: i32 = { + let x: &dyn Foo = &S; + x.bar() + x.foo() + }; + "#, + 900, + ); +} + +#[test] +fn boxes() { + check_number( + r#" +//- minicore: coerce_unsized, deref_mut, slice +use core::ops::{Deref, DerefMut}; +use core::{marker::Unsize, ops::CoerceUnsized}; + +#[lang = "owned_box"] +pub struct Box { + inner: *mut T, +} +impl Box { + fn new(t: T) -> Self { + #[rustc_box] + Box::new(t) + } +} + +impl Deref for Box { + type Target = T; + + fn deref(&self) -> &T { + &**self + } +} + +impl DerefMut for Box { + fn deref_mut(&mut self) -> &mut T { + &mut **self + } +} + +impl, U: ?Sized> CoerceUnsized> for Box {} + +const GOAL: usize = { + let x = Box::new(5); + let y: Box<[i32]> = Box::new([1, 2, 3]); + *x + y.len() +}; +"#, + 8, + ); +} + #[test] fn array_and_index() { check_number( @@ -867,9 +1922,42 @@ fn array_and_index() { check_number( r#" //- minicore: coerce_unsized, index, slice + const GOAL: usize = { + let a = [1, 2, 3]; + let x: &[i32] = &a; + let y = &*x; + y.len() + };"#, + 3, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice const GOAL: usize = [1, 2, 3, 4, 5].len();"#, 5, ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: [u16; 5] = [1, 2, 3, 4, 5];"#, + 1 + (2 << 16) + (3 << 32) + (4 << 48) + (5 << 64), + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: [u16; 5] = [12; 5];"#, + 12 + (12 << 16) + (12 << 32) + (12 << 48) + (12 << 64), + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const LEN: usize = 4; + const GOAL: u16 = { + let x = [7; LEN]; + x[2] + }"#, + 7, + ); } #[test] @@ -887,6 +1975,38 @@ fn byte_string() { ); } +#[test] +fn c_string() { + check_number( + r#" +//- minicore: index, slice +#[lang = "CStr"] +pub struct CStr { + inner: [u8] +} +const GOAL: u8 = { + let a = c"hello"; + a.inner[0] +}; + "#, + 104, + ); + check_number( + r#" +//- minicore: index, slice +#[lang = "CStr"] +pub struct CStr { + inner: [u8] +} +const GOAL: u8 = { + let a = c"hello"; + a.inner[6] +}; + "#, + 0, + ); +} + #[test] fn consts() { check_number( @@ -900,6 +2020,37 @@ fn consts() { ); } +#[test] +fn statics() { + check_number( + r#" + //- minicore: cell + use core::cell::Cell; + fn f() -> i32 { + static S: Cell = Cell::new(10); + S.set(S.get() + 1); + S.get() + } + const GOAL: i32 = f() + f() + f(); + "#, + 36, + ); +} + +#[test] +fn extern_weak_statics() { + check_number( + r#" + extern "C" { + #[linkage = "extern_weak"] + static __dso_handle: *mut u8; + } + const GOAL: usize = __dso_handle as usize; + "#, + 0, + ); +} + #[test] fn enums() { check_number( @@ -927,14 +2078,14 @@ enum E { F1, } "#, 0, ); - let r = eval_goal( + let (db, file_id) = TestDB::with_single_file( r#" enum E { A = 1, B } const GOAL: E = E::A; "#, - ) - .unwrap(); - assert_eq!(try_const_usize(&r), Some(1)); + ); + let r = eval_goal(&db, file_id).unwrap(); + assert_eq!(try_const_usize(&db, &r), Some(1)); } #[test] @@ -946,7 +2097,7 @@ fn const_loop() { const F2: i32 = 2 * F1; const GOAL: i32 = F3; "#, - ConstEvalError::MirLowerError(MirLowerError::Loop), + |e| e == ConstEvalError::MirLowerError(MirLowerError::Loop), ); } @@ -962,6 +2113,29 @@ fn const_transfer_memory() { ); } +#[test] +fn anonymous_const_block() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn size_of() -> usize; + } + + const fn f() -> usize { + let r = const { size_of::() }; + r + } + + const GOAL: usize = { + let x = const { 2 + const { 3 } }; + let y = f::(); + x + y + }; + "#, + 9, + ); +} + #[test] fn const_impl_assoc() { check_number( @@ -970,9 +2144,9 @@ fn const_impl_assoc() { impl U5 { const VAL: usize = 5; } - const GOAL: usize = U5::VAL; + const GOAL: usize = U5::VAL + ::VAL; "#, - 5, + 10, ); } @@ -987,12 +2161,61 @@ const fn f(x: usize) -> usize { "#, 11, ); + check_number( + r#" + fn f(x: [i32; N]) -> usize { + N + } + + trait ArrayExt { + fn f(self) -> usize; + } + + impl ArrayExt for [T; N] { + fn g(self) -> usize { + f(self) + } + } + + const GOAL: usize = f([1, 2, 5]); + "#, + 3, + ); +} + +#[test] +fn layout_of_type_with_associated_type_field_defined_inside_body() { + check_number( + r#" +trait Tr { + type Ty; +} + +struct St(T::Ty); + +const GOAL: i64 = { + // if we move `St2` out of body, the test will fail, as we don't see the impl anymore. That + // case will probably be rejected by rustc in some later edition, but we should support this + // case. + struct St2; + + impl Tr for St2 { + type Ty = i64; + } + + struct Goal(St); + + let x = Goal(St(5)); + x.0.0 +}; +"#, + 5, + ); } #[test] fn const_generic_subst_assoc_const_impl() { - // FIXME: this should evaluate to 5 - check_fail( + check_number( r#" struct Adder; impl Adder { @@ -1000,14 +2223,42 @@ impl Adder { } const GOAL: usize = Adder::<2, 3>::VAL; "#, - ConstEvalError::MirEvalError(MirEvalError::TypeError("missing generic arg")), + 5, + ); +} + +#[test] +fn associated_types() { + check_number( + r#" + trait Tr { + type Item; + fn get_item(&self) -> Self::Item; + } + + struct X(i32); + struct Y(i32); + + impl Tr for X { + type Item = Y; + fn get_item(&self) -> Self::Item { + Y(self.0 + 2) + } + } + + fn my_get_item(x: T) -> ::Item { + x.get_item() + } + + const GOAL: i32 = my_get_item(X(3)).0; + "#, + 5, ); } #[test] fn const_trait_assoc() { - // FIXME: this should evaluate to 0 - check_fail( + check_number( r#" struct U0; trait ToConst { @@ -1016,9 +2267,49 @@ trait ToConst { impl ToConst for U0 { const VAL: usize = 0; } - const GOAL: usize = U0::VAL; + impl ToConst for i32 { + const VAL: usize = 32; + } + const GOAL: usize = U0::VAL + i32::VAL; "#, - ConstEvalError::MirLowerError(MirLowerError::IncompleteExpr), + 32, + ); + check_number( + r#" + struct S(*mut T); + + trait MySized: Sized { + const SIZE: S = S(1 as *mut Self); + } + + impl MySized for i32 { + const SIZE: S = S(10 as *mut i32); + } + + impl MySized for i64 { + } + + const fn f() -> usize { + T::SIZE.0 as usize + } + + const GOAL: usize = f::() + f::() * 2; + "#, + 12, + ); +} + +#[test] +fn panic_messages() { + check_fail( + r#" + //- minicore: panic + const GOAL: u8 = { + let x: u16 = 2; + panic!("hello"); + }; + "#, + |e| e == ConstEvalError::MirEvalError(MirEvalError::Panic("hello".to_string())), ); } @@ -1028,7 +2319,7 @@ fn exec_limits() { r#" const GOAL: usize = loop {}; "#, - ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded), + |e| e == ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded), ); check_fail( r#" @@ -1037,7 +2328,7 @@ const fn f(x: i32) -> i32 { } const GOAL: i32 = f(0); "#, - ConstEvalError::MirEvalError(MirEvalError::StackOverflow), + |e| e == ConstEvalError::MirEvalError(MirEvalError::StackOverflow), ); // Reasonable code should still work check_number( @@ -1062,7 +2353,7 @@ const fn f(n: i32) -> i32 { #[test] fn type_error() { - let e = eval_goal( + check_fail( r#" const GOAL: u8 = { let x: u16 = 2; @@ -1070,6 +2361,25 @@ fn type_error() { y.0 }; "#, + |e| matches!(e, ConstEvalError::MirLowerError(MirLowerError::TypeMismatch(_))), + ); +} + +#[test] +fn unsized_local() { + check_fail( + r#" + //- minicore: coerce_unsized, index, slice + const fn x() -> SomeUnknownTypeThatDereferenceToSlice { + SomeUnknownTypeThatDereferenceToSlice + } + + const GOAL: u16 = { + let y = x(); + let z: &[u16] = &y; + z[1] + }; + "#, + |e| matches!(e, ConstEvalError::MirLowerError(MirLowerError::UnsizedTemporary(_))), ); - assert!(matches!(e, Err(ConstEvalError::MirLowerError(MirLowerError::TypeMismatch(_))))); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs new file mode 100644 index 000000000000..e05d824dbacf --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -0,0 +1,377 @@ +use super::*; + +#[test] +fn size_of() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn size_of() -> usize; + } + + const GOAL: usize = size_of::(); + "#, + 4, + ); +} + +#[test] +fn transmute() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn transmute(e: T) -> U; + } + + const GOAL: i32 = transmute((1i16, 1i16)); + "#, + 0x00010001, + ); +} + +#[test] +fn const_eval_select() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn const_eval_select(arg: ARG, called_in_const: F, called_at_rt: G) -> RET + where + G: FnOnce, + F: FnOnce; + } + + const fn in_const(x: i32, y: i32) -> i32 { + x + y + } + + fn in_rt(x: i32, y: i32) -> i32 { + x + y + } + + const GOAL: i32 = const_eval_select((2, 3), in_const, in_rt); + "#, + 5, + ); +} + +#[test] +fn wrapping_add() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn wrapping_add(a: T, b: T) -> T; + } + + const GOAL: u8 = wrapping_add(10, 250); + "#, + 4, + ); +} + +#[test] +fn saturating_add() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn saturating_add(a: T, b: T) -> T; + } + + const GOAL: u8 = saturating_add(10, 250); + "#, + 255, + ); + check_number( + r#" + extern "rust-intrinsic" { + pub fn saturating_add(a: T, b: T) -> T; + } + + const GOAL: i8 = saturating_add(5, 8); + "#, + 13, + ); +} + +#[test] +fn allocator() { + check_number( + r#" + extern "Rust" { + #[rustc_allocator] + fn __rust_alloc(size: usize, align: usize) -> *mut u8; + #[rustc_deallocator] + fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); + #[rustc_reallocator] + fn __rust_realloc(ptr: *mut u8, old_size: usize, align: usize, new_size: usize) -> *mut u8; + #[rustc_allocator_zeroed] + fn __rust_alloc_zeroed(size: usize, align: usize) -> *mut u8; + } + + const GOAL: u8 = unsafe { + let ptr = __rust_alloc(4, 1); + let ptr2 = ((ptr as usize) + 1) as *mut u8; + *ptr = 23; + *ptr2 = 32; + let ptr = __rust_realloc(ptr, 4, 1, 8); + let ptr2 = ((ptr as usize) + 1) as *mut u8; + *ptr + *ptr2 + }; + "#, + 55, + ); +} + +#[test] +fn overflowing_add() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn add_with_overflow(x: T, y: T) -> (T, bool); + } + + const GOAL: u8 = add_with_overflow(1, 2).0; + "#, + 3, + ); + check_number( + r#" + extern "rust-intrinsic" { + pub fn add_with_overflow(x: T, y: T) -> (T, bool); + } + + const GOAL: u8 = add_with_overflow(1, 2).1 as u8; + "#, + 0, + ); +} + +#[test] +fn needs_drop() { + check_number( + r#" + //- minicore: copy, sized + extern "rust-intrinsic" { + pub fn needs_drop() -> bool; + } + struct X; + const GOAL: bool = !needs_drop::() && needs_drop::(); + "#, + 1, + ); +} + +#[test] +fn likely() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn likely(b: bool) -> bool; + pub fn unlikely(b: bool) -> bool; + } + + const GOAL: bool = likely(true) && unlikely(true) && !likely(false) && !unlikely(false); + "#, + 1, + ); +} + +#[test] +fn floating_point() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn sqrtf32(x: f32) -> f32; + pub fn powf32(a: f32, x: f32) -> f32; + pub fn fmaf32(a: f32, b: f32, c: f32) -> f32; + } + + const GOAL: f32 = sqrtf32(1.2) + powf32(3.4, 5.6) + fmaf32(-7.8, 1.3, 2.4); + "#, + i128::from_le_bytes(pad16( + &f32::to_le_bytes(1.2f32.sqrt() + 3.4f32.powf(5.6) + (-7.8f32).mul_add(1.3, 2.4)), + true, + )), + ); + check_number( + r#" + extern "rust-intrinsic" { + pub fn powif64(a: f64, x: i32) -> f64; + pub fn sinf64(x: f64) -> f64; + pub fn minnumf64(x: f64, y: f64) -> f64; + } + + const GOAL: f64 = powif64(1.2, 5) + sinf64(3.4) + minnumf64(-7.8, 1.3); + "#, + i128::from_le_bytes(pad16( + &f64::to_le_bytes(1.2f64.powi(5) + 3.4f64.sin() + (-7.8f64).min(1.3)), + true, + )), + ); +} + +#[test] +fn atomic() { + check_number( + r#" + //- minicore: copy + extern "rust-intrinsic" { + pub fn atomic_load_seqcst(src: *const T) -> T; + pub fn atomic_xchg_acquire(dst: *mut T, src: T) -> T; + pub fn atomic_cxchg_release_seqcst(dst: *mut T, old: T, src: T) -> (T, bool); + pub fn atomic_cxchgweak_acquire_acquire(dst: *mut T, old: T, src: T) -> (T, bool); + pub fn atomic_store_release(dst: *mut T, val: T); + pub fn atomic_xadd_acqrel(dst: *mut T, src: T) -> T; + pub fn atomic_xsub_seqcst(dst: *mut T, src: T) -> T; + pub fn atomic_and_acquire(dst: *mut T, src: T) -> T; + pub fn atomic_nand_seqcst(dst: *mut T, src: T) -> T; + pub fn atomic_or_release(dst: *mut T, src: T) -> T; + pub fn atomic_xor_seqcst(dst: *mut T, src: T) -> T; + } + + fn should_not_reach() { + _ // fails the test if executed + } + + const GOAL: i32 = { + let mut x = 5; + atomic_store_release(&mut x, 10); + let mut y = atomic_xchg_acquire(&mut x, 100); + atomic_xadd_acqrel(&mut y, 20); + if (30, true) != atomic_cxchg_release_seqcst(&mut y, 30, 40) { + should_not_reach(); + } + if (40, false) != atomic_cxchg_release_seqcst(&mut y, 30, 50) { + should_not_reach(); + } + if (40, true) != atomic_cxchgweak_acquire_acquire(&mut y, 40, 30) { + should_not_reach(); + } + let mut z = atomic_xsub_seqcst(&mut x, -200); + atomic_xor_seqcst(&mut x, 1024); + atomic_load_seqcst(&x) + z * 3 + atomic_load_seqcst(&y) * 2 + }; + "#, + 660 + 1024, + ); +} + +#[test] +fn offset() { + check_number( + r#" + //- minicore: coerce_unsized, index, slice + extern "rust-intrinsic" { + pub fn offset(dst: *const T, offset: isize) -> *const T; + } + + const GOAL: u8 = unsafe { + let ar: &[(u8, u8, u8)] = &[ + (10, 11, 12), + (20, 21, 22), + (30, 31, 32), + (40, 41, 42), + (50, 51, 52), + ]; + let ar: *const [(u8, u8, u8)] = ar; + let ar = ar as *const (u8, u8, u8); + let element = *offset(ar, 2); + element.1 + }; + "#, + 31, + ); +} + +#[test] +fn arith_offset() { + check_number( + r#" + //- minicore: coerce_unsized, index, slice + extern "rust-intrinsic" { + pub fn arith_offset(dst: *const T, offset: isize) -> *const T; + } + + const GOAL: u8 = unsafe { + let ar: &[(u8, u8, u8)] = &[ + (10, 11, 12), + (20, 21, 22), + (30, 31, 32), + (40, 41, 42), + (50, 51, 52), + ]; + let ar: *const [(u8, u8, u8)] = ar; + let ar = ar as *const (u8, u8, u8); + let element = *arith_offset(arith_offset(ar, 102), -100); + element.1 + }; + "#, + 31, + ); +} + +#[test] +fn copy_nonoverlapping() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); + } + + const GOAL: u8 = unsafe { + let mut x = 2; + let y = 5; + copy_nonoverlapping(&y, &mut x, 1); + x + }; + "#, + 5, + ); +} + +#[test] +fn copy() { + check_number( + r#" + //- minicore: coerce_unsized, index, slice + extern "rust-intrinsic" { + pub fn copy(src: *const T, dst: *mut T, count: usize); + } + + const GOAL: i32 = unsafe { + let mut x = [1i32, 2, 3, 4, 5]; + let y = (&mut x as *mut _) as *mut i32; + let z = (y as usize + 4) as *const i32; + copy(z, y, 4); + x[0] + x[1] + x[2] + x[3] + x[4] + }; + "#, + 19, + ); +} + +#[test] +fn ctpop() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn ctpop(x: T) -> T; + } + + const GOAL: i64 = ctpop(-29); + "#, + 61, + ); +} + +#[test] +fn cttz() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn cttz(x: T) -> T; + } + + const GOAL: i64 = cttz(-24); + "#, + 3, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index 304c78767f12..ca8a394e360d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -1,27 +1,27 @@ //! The home of `HirDatabase`, which is the Salsa database containing all the //! type inference-related queries. -use std::sync::Arc; +use std::sync; use base_db::{impl_intern_key, salsa, CrateId, Upcast}; use hir_def::{ - db::DefDatabase, - expr::ExprId, - layout::{Layout, LayoutError, TargetDataLayout}, - AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, GenericDefId, - ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId, + db::DefDatabase, hir::ExprId, layout::TargetDataLayout, AdtId, BlockId, ConstParamId, + DefWithBodyId, EnumVariantId, FunctionId, GeneralConstId, GenericDefId, ImplId, + LifetimeParamId, LocalFieldId, StaticId, TypeOrConstParamId, VariantId, }; use la_arena::ArenaMap; use smallvec::SmallVec; +use triomphe::Arc; use crate::{ chalk_db, consteval::ConstEvalError, + layout::{Layout, LayoutError}, method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, mir::{BorrowckResult, MirBody, MirLowerError}, - Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, - PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, - ValueTyDefId, + Binders, CallableDefId, ClosureId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, + Interner, PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, + TyDefId, ValueTyDefId, }; use hir_expand::name::Name; @@ -38,8 +38,28 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::cycle(crate::mir::mir_body_recover)] fn mir_body(&self, def: DefWithBodyId) -> Result, MirLowerError>; + #[salsa::invoke(crate::mir::mir_body_for_closure_query)] + fn mir_body_for_closure(&self, def: ClosureId) -> Result, MirLowerError>; + + #[salsa::invoke(crate::mir::monomorphized_mir_body_query)] + #[salsa::cycle(crate::mir::monomorphized_mir_body_recover)] + fn monomorphized_mir_body( + &self, + def: DefWithBodyId, + subst: Substitution, + env: Arc, + ) -> Result, MirLowerError>; + + #[salsa::invoke(crate::mir::monomorphized_mir_body_for_closure_query)] + fn monomorphized_mir_body_for_closure( + &self, + def: ClosureId, + subst: Substitution, + env: Arc, + ) -> Result, MirLowerError>; + #[salsa::invoke(crate::mir::borrowck_query)] - fn borrowck(&self, def: DefWithBodyId) -> Result, MirLowerError>; + fn borrowck(&self, def: DefWithBodyId) -> Result, MirLowerError>; #[salsa::invoke(crate::lower::ty_query)] #[salsa::cycle(crate::lower::ty_recover)] @@ -57,7 +77,12 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::invoke(crate::consteval::const_eval_query)] #[salsa::cycle(crate::consteval::const_eval_recover)] - fn const_eval(&self, def: ConstId) -> Result; + fn const_eval(&self, def: GeneralConstId, subst: Substitution) + -> Result; + + #[salsa::invoke(crate::consteval::const_eval_static_query)] + #[salsa::cycle(crate::consteval::const_eval_static_recover)] + fn const_eval_static(&self, def: StaticId) -> Result; #[salsa::invoke(crate::consteval::const_eval_discriminant_variant)] #[salsa::cycle(crate::consteval::const_eval_discriminant_recover)] @@ -71,7 +96,16 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::invoke(crate::layout::layout_of_adt_query)] #[salsa::cycle(crate::layout::layout_of_adt_recover)] - fn layout_of_adt(&self, def: AdtId, subst: Substitution) -> Result; + fn layout_of_adt( + &self, + def: AdtId, + subst: Substitution, + krate: CrateId, + ) -> Result, LayoutError>; + + #[salsa::invoke(crate::layout::layout_of_ty_query)] + #[salsa::cycle(crate::layout::layout_of_ty_recover)] + fn layout_of_ty(&self, ty: Ty, krate: CrateId) -> Result, LayoutError>; #[salsa::invoke(crate::layout::target_data_layout_query)] fn target_data_layout(&self, krate: CrateId) -> Option>; @@ -97,6 +131,10 @@ fn generic_predicates_for_param( #[salsa::invoke(crate::lower::generic_predicates_query)] fn generic_predicates(&self, def: GenericDefId) -> Arc<[Binders]>; + #[salsa::invoke(crate::lower::trait_environment_for_body_query)] + #[salsa::transparent] + fn trait_environment_for_body(&self, def: DefWithBodyId) -> Arc; + #[salsa::invoke(crate::lower::trait_environment_query)] fn trait_environment(&self, def: GenericDefId) -> Arc; @@ -108,7 +146,7 @@ fn generic_predicates_for_param( fn inherent_impls_in_crate(&self, krate: CrateId) -> Arc; #[salsa::invoke(InherentImpls::inherent_impls_in_block_query)] - fn inherent_impls_in_block(&self, block: BlockId) -> Option>; + fn inherent_impls_in_block(&self, block: BlockId) -> Arc; /// Collects all crates in the dependency graph that have impls for the /// given fingerprint. This is only used for primitive types and types @@ -125,10 +163,10 @@ fn incoherent_inherent_impl_crates( fn trait_impls_in_crate(&self, krate: CrateId) -> Arc; #[salsa::invoke(TraitImpls::trait_impls_in_block_query)] - fn trait_impls_in_block(&self, krate: BlockId) -> Option>; + fn trait_impls_in_block(&self, block: BlockId) -> Arc; #[salsa::invoke(TraitImpls::trait_impls_in_deps_query)] - fn trait_impls_in_deps(&self, krate: CrateId) -> Arc; + fn trait_impls_in_deps(&self, krate: CrateId) -> Arc<[Arc]>; // Interned IDs for Chalk integration #[salsa::interned] @@ -148,24 +186,34 @@ fn intern_type_or_const_param_id( 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; + fn associated_ty_data( + &self, + id: chalk_db::AssocTypeId, + ) -> sync::Arc; #[salsa::invoke(chalk_db::trait_datum_query)] - fn trait_datum(&self, krate: CrateId, trait_id: chalk_db::TraitId) - -> Arc; + fn trait_datum( + &self, + krate: CrateId, + trait_id: chalk_db::TraitId, + ) -> sync::Arc; #[salsa::invoke(chalk_db::struct_datum_query)] fn struct_datum( &self, krate: CrateId, struct_id: chalk_db::AdtId, - ) -> Arc; + ) -> sync::Arc; #[salsa::invoke(chalk_db::impl_datum_query)] - fn impl_datum(&self, krate: CrateId, impl_id: chalk_db::ImplId) -> Arc; + fn impl_datum( + &self, + krate: CrateId, + impl_id: chalk_db::ImplId, + ) -> sync::Arc; #[salsa::invoke(chalk_db::fn_def_datum_query)] - fn fn_def_datum(&self, krate: CrateId, fn_def_id: FnDefId) -> Arc; + fn fn_def_datum(&self, krate: CrateId, fn_def_id: FnDefId) -> sync::Arc; #[salsa::invoke(chalk_db::fn_def_variance_query)] fn fn_def_variance(&self, fn_def_id: FnDefId) -> chalk_db::Variances; @@ -178,7 +226,7 @@ fn associated_ty_value( &self, krate: CrateId, id: chalk_db::AssociatedTyValueId, - ) -> Arc; + ) -> sync::Arc; #[salsa::invoke(crate::traits::normalize_projection_query)] #[salsa::transparent] @@ -193,6 +241,7 @@ fn normalize_projection( fn trait_solve( &self, krate: CrateId, + block: Option, goal: crate::Canonical>, ) -> Option; @@ -200,6 +249,7 @@ fn trait_solve( fn trait_solve_query( &self, krate: CrateId, + block: Option, goal: crate::Canonical>, ) -> Option; @@ -207,19 +257,26 @@ fn trait_solve_query( fn program_clauses_for_chalk_env( &self, krate: CrateId, + block: Option, env: chalk_ir::Environment, ) -> chalk_ir::ProgramClauses; } fn infer_wait(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc { let _p = profile::span("infer:wait").detail(|| match def { - DefWithBodyId::FunctionId(it) => db.function_data(it).name.to_string(), - DefWithBodyId::StaticId(it) => db.static_data(it).name.clone().to_string(), - DefWithBodyId::ConstId(it) => { - db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string() + DefWithBodyId::FunctionId(it) => db.function_data(it).name.display(db.upcast()).to_string(), + DefWithBodyId::StaticId(it) => { + db.static_data(it).name.clone().display(db.upcast()).to_string() } + DefWithBodyId::ConstId(it) => db + .const_data(it) + .name + .clone() + .unwrap_or_else(Name::missing) + .display(db.upcast()) + .to_string(), DefWithBodyId::VariantId(it) => { - db.enum_data(it.parent).variants[it.local_id].name.to_string() + db.enum_data(it.parent).variants[it.local_id].name.display(db.upcast()).to_string() } }); db.infer_query(def) @@ -228,10 +285,11 @@ fn infer_wait(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc fn trait_solve_wait( db: &dyn HirDatabase, krate: CrateId, + block: Option, goal: crate::Canonical>, ) -> Option { let _p = profile::span("trait_solve::wait"); - db.trait_solve_query(krate, goal) + db.trait_solve_query(krate, block, goal) } #[test] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index d36b93e3bdde..1233469b947b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -16,8 +16,8 @@ use base_db::CrateId; use hir_def::{ - adt::VariantData, - expr::{Pat, PatId}, + data::adt::VariantData, + hir::{Pat, PatId}, src::HasSource, AdtId, AttrDefId, ConstId, EnumId, FunctionId, ItemContainerId, Lookup, ModuleDefId, StaticId, StructId, @@ -223,7 +223,7 @@ fn validate_func(&mut self, func: FunctionId) { } // Check the function name. - let function_name = data.name.to_string(); + let function_name = data.name.display(self.db.upcast()).to_string(); let fn_name_replacement = to_lower_snake_case(&function_name).map(|new_name| Replacement { current_name: data.name.clone(), suggested_text: new_name, @@ -244,7 +244,9 @@ fn validate_func(&mut self, func: FunctionId) { id, Replacement { current_name: bind_name.clone(), - suggested_text: to_lower_snake_case(&bind_name.to_string())?, + suggested_text: to_lower_snake_case( + &bind_name.display(self.db.upcast()).to_string(), + )?, expected_case: CaseType::LowerSnakeCase, }, )) @@ -287,7 +289,7 @@ fn create_incorrect_case_diagnostic_for_func( ident_type: IdentType::Function, ident: AstPtr::new(&ast_ptr), expected_case: fn_name_replacement.expected_case, - ident_text: fn_name_replacement.current_name.to_string(), + ident_text: fn_name_replacement.current_name.display(self.db.upcast()).to_string(), suggested_text: fn_name_replacement.suggested_text, }; @@ -343,7 +345,10 @@ fn create_incorrect_case_diagnostic_for_variables( ident_type, ident: AstPtr::new(&name_ast), expected_case: replacement.expected_case, - ident_text: replacement.current_name.to_string(), + ident_text: replacement + .current_name + .display(self.db.upcast()) + .to_string(), suggested_text: replacement.suggested_text, }; @@ -362,7 +367,7 @@ fn validate_struct(&mut self, struct_id: StructId) { let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE, false); // Check the structure name. - let struct_name = data.name.to_string(); + let struct_name = data.name.display(self.db.upcast()).to_string(); let struct_name_replacement = if !non_camel_case_allowed { to_camel_case(&struct_name).map(|new_name| Replacement { current_name: data.name.clone(), @@ -379,7 +384,7 @@ fn validate_struct(&mut self, struct_id: StructId) { if !non_snake_case_allowed { if let VariantData::Record(fields) = data.variant_data.as_ref() { for (_, field) in fields.iter() { - let field_name = field.name.to_string(); + let field_name = field.name.display(self.db.upcast()).to_string(); if let Some(new_name) = to_lower_snake_case(&field_name) { let replacement = Replacement { current_name: field.name.clone(), @@ -434,7 +439,7 @@ fn create_incorrect_case_diagnostic_for_struct( ident_type: IdentType::Structure, ident: AstPtr::new(&ast_ptr), expected_case: replacement.expected_case, - ident_text: replacement.current_name.to_string(), + ident_text: replacement.current_name.display(self.db.upcast()).to_string(), suggested_text: replacement.suggested_text, }; @@ -479,7 +484,7 @@ fn create_incorrect_case_diagnostic_for_struct( ident_type: IdentType::Field, ident: AstPtr::new(&ast_ptr), expected_case: field_to_rename.expected_case, - ident_text: field_to_rename.current_name.to_string(), + ident_text: field_to_rename.current_name.display(self.db.upcast()).to_string(), suggested_text: field_to_rename.suggested_text, }; @@ -496,7 +501,7 @@ fn validate_enum(&mut self, enum_id: EnumId) { } // Check the enum name. - let enum_name = data.name.to_string(); + let enum_name = data.name.display(self.db.upcast()).to_string(); let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement { current_name: data.name.clone(), suggested_text: new_name, @@ -510,7 +515,9 @@ fn validate_enum(&mut self, enum_id: EnumId) { .filter_map(|(_, variant)| { Some(Replacement { current_name: variant.name.clone(), - suggested_text: to_camel_case(&variant.name.to_string())?, + suggested_text: to_camel_case( + &variant.name.display(self.db.upcast()).to_string(), + )?, expected_case: CaseType::UpperCamelCase, }) }) @@ -558,7 +565,7 @@ fn create_incorrect_case_diagnostic_for_enum( ident_type: IdentType::Enum, ident: AstPtr::new(&ast_ptr), expected_case: replacement.expected_case, - ident_text: replacement.current_name.to_string(), + ident_text: replacement.current_name.display(self.db.upcast()).to_string(), suggested_text: replacement.suggested_text, }; @@ -603,7 +610,7 @@ fn create_incorrect_case_diagnostic_for_enum( ident_type: IdentType::Variant, ident: AstPtr::new(&ast_ptr), expected_case: variant_to_rename.expected_case, - ident_text: variant_to_rename.current_name.to_string(), + ident_text: variant_to_rename.current_name.display(self.db.upcast()).to_string(), suggested_text: variant_to_rename.suggested_text, }; @@ -623,7 +630,7 @@ fn validate_const(&mut self, const_id: ConstId) { None => return, }; - let const_name = name.to_string(); + let const_name = name.display(self.db.upcast()).to_string(); let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) { Replacement { current_name: name.clone(), @@ -648,7 +655,7 @@ fn validate_const(&mut self, const_id: ConstId) { ident_type: IdentType::Constant, ident: AstPtr::new(&ast_ptr), expected_case: replacement.expected_case, - ident_text: replacement.current_name.to_string(), + ident_text: replacement.current_name.display(self.db.upcast()).to_string(), suggested_text: replacement.suggested_text, }; @@ -668,7 +675,7 @@ fn validate_static(&mut self, static_id: StaticId) { let name = &data.name; - let static_name = name.to_string(); + let static_name = name.display(self.db.upcast()).to_string(); let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) { Replacement { current_name: name.clone(), @@ -693,7 +700,7 @@ fn validate_static(&mut self, static_id: StaticId) { ident_type: IdentType::StaticVariable, ident: AstPtr::new(&ast_ptr), expected_case: replacement.expected_case, - ident_text: replacement.current_name.to_string(), + ident_text: replacement.current_name.display(self.db.upcast()).to_string(), suggested_text: replacement.suggested_text, }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs index 2e9066788cf6..ab34dc88d87b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs @@ -3,7 +3,6 @@ //! fields, etc. use std::fmt; -use std::sync::Arc; use either::Either; use hir_def::lang_item::LangItem; @@ -12,6 +11,7 @@ use hir_expand::name; use itertools::Itertools; use rustc_hash::FxHashSet; +use triomphe::Arc; use typed_arena::Arena; use crate::{ @@ -27,7 +27,7 @@ pub(crate) use hir_def::{ body::Body, - expr::{Expr, ExprId, MatchArm, Pat, PatId}, + hir::{Expr, ExprId, MatchArm, Pat, PatId}, LocalFieldId, VariantId, }; @@ -207,7 +207,7 @@ fn validate_match( let report = compute_match_usefulness(&cx, &m_arms, scrut_ty); - // FIXME Report unreacheble arms + // FIXME Report unreachable arms // https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200 let witnesses = report.non_exhaustiveness_witnesses; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs index 859a37804ae7..f8cdeaa5e354 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs @@ -1,6 +1,6 @@ //! Validation of matches. //! -//! This module provides lowering from [hir_def::expr::Pat] to [self::Pat] and match +//! This module provides lowering from [hir_def::hir::Pat] to [self::Pat] and match //! checking algorithm. //! //! It is modeled on the rustc module `rustc_mir_build::thir::pattern`. @@ -12,7 +12,7 @@ use chalk_ir::Mutability; use hir_def::{ - adt::VariantData, body::Body, expr::PatId, AdtId, EnumVariantId, LocalFieldId, VariantId, + body::Body, data::adt::VariantData, hir::PatId, AdtId, EnumVariantId, LocalFieldId, VariantId, }; use hir_expand::name::Name; use stdx::{always, never}; @@ -125,15 +125,15 @@ fn lower_pattern_unadjusted(&mut self, pat: PatId) -> Pat { let variant = self.infer.variant_resolution_for_pat(pat); let kind = match self.body[pat] { - hir_def::expr::Pat::Wild => PatKind::Wild, + hir_def::hir::Pat::Wild => PatKind::Wild, - hir_def::expr::Pat::Lit(expr) => self.lower_lit(expr), + hir_def::hir::Pat::Lit(expr) => self.lower_lit(expr), - hir_def::expr::Pat::Path(ref path) => { + hir_def::hir::Pat::Path(ref path) => { return self.lower_path(pat, path); } - hir_def::expr::Pat::Tuple { ref args, ellipsis } => { + hir_def::hir::Pat::Tuple { ref args, ellipsis } => { let arity = match *ty.kind(Interner) { TyKind::Tuple(arity, _) => arity, _ => { @@ -146,13 +146,14 @@ fn lower_pattern_unadjusted(&mut self, pat: PatId) -> Pat { PatKind::Leaf { subpatterns } } - hir_def::expr::Pat::Bind { id, subpat, .. } => { - let bm = self.infer.pat_binding_modes[&pat]; + hir_def::hir::Pat::Bind { id, subpat, .. } => { + let bm = self.infer.binding_modes[id]; + ty = &self.infer[id]; let name = &self.body.bindings[id].name; match (bm, ty.kind(Interner)) { (BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty, (BindingMode::Ref(_), _) => { - never!("`ref {}` has wrong type {:?}", name, ty); + never!("`ref {}` has wrong type {:?}", name.display(self.db.upcast()), ty); self.errors.push(PatternError::UnexpectedType); return Pat { ty: ty.clone(), kind: PatKind::Wild.into() }; } @@ -161,13 +162,13 @@ fn lower_pattern_unadjusted(&mut self, pat: PatId) -> Pat { PatKind::Binding { name: name.clone(), subpattern: self.lower_opt_pattern(subpat) } } - hir_def::expr::Pat::TupleStruct { ref args, ellipsis, .. } if variant.is_some() => { + hir_def::hir::Pat::TupleStruct { ref args, ellipsis, .. } if variant.is_some() => { let expected_len = variant.unwrap().variant_data(self.db.upcast()).fields().len(); let subpatterns = self.lower_tuple_subpats(args, expected_len, ellipsis); self.lower_variant_or_leaf(pat, ty, subpatterns) } - hir_def::expr::Pat::Record { ref args, .. } if variant.is_some() => { + hir_def::hir::Pat::Record { ref args, .. } if variant.is_some() => { let variant_data = variant.unwrap().variant_data(self.db.upcast()); let subpatterns = args .iter() @@ -187,12 +188,12 @@ fn lower_pattern_unadjusted(&mut self, pat: PatId) -> Pat { } } } - hir_def::expr::Pat::TupleStruct { .. } | hir_def::expr::Pat::Record { .. } => { + hir_def::hir::Pat::TupleStruct { .. } | hir_def::hir::Pat::Record { .. } => { self.errors.push(PatternError::UnresolvedVariant); PatKind::Wild } - hir_def::expr::Pat::Or(ref pats) => PatKind::Or { pats: self.lower_patterns(pats) }, + hir_def::hir::Pat::Or(ref pats) => PatKind::Or { pats: self.lower_patterns(pats) }, _ => { self.errors.push(PatternError::Unimplemented); @@ -279,8 +280,8 @@ fn lower_path(&mut self, pat: PatId, _path: &hir_def::path::Path) -> Pat { } } - fn lower_lit(&mut self, expr: hir_def::expr::ExprId) -> PatKind { - use hir_def::expr::{Expr, Literal::Bool}; + fn lower_lit(&mut self, expr: hir_def::hir::ExprId) -> PatKind { + use hir_def::hir::{Expr, Literal::Bool}; match self.body[expr] { Expr::Literal(Bool(value)) => PatKind::LiteralBool { value }, @@ -297,7 +298,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match &*self.kind { PatKind::Wild => write!(f, "_"), PatKind::Binding { name, subpattern } => { - write!(f, "{name}")?; + write!(f, "{}", name.display(f.db.upcast()))?; if let Some(subpattern) = subpattern { write!(f, " @ ")?; subpattern.hir_fmt(f)?; @@ -318,10 +319,14 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match variant { VariantId::EnumVariantId(v) => { let data = f.db.enum_data(v.parent); - write!(f, "{}", data.variants[v.local_id].name)?; + write!(f, "{}", data.variants[v.local_id].name.display(f.db.upcast()))?; + } + VariantId::StructId(s) => { + write!(f, "{}", f.db.struct_data(s).name.display(f.db.upcast()))? + } + VariantId::UnionId(u) => { + write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast()))? } - VariantId::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?, - VariantId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name)?, }; let variant_data = variant.variant_data(f.db.upcast()); @@ -335,7 +340,11 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { .map(|p| { printed += 1; WriteWith(move |f| { - write!(f, "{}: ", rec_fields[p.field].name)?; + write!( + f, + "{}: ", + rec_fields[p.field].name.display(f.db.upcast()) + )?; p.pattern.hir_fmt(f) }) }); @@ -379,7 +388,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { } PatKind::Deref { subpattern } => { match self.ty.kind(Interner) { - TyKind::Adt(adt, _) if is_box(adt.0, f.db) => write!(f, "box ")?, + TyKind::Adt(adt, _) if is_box(f.db, adt.0) => write!(f, "box ")?, &TyKind::Ref(mutbl, ..) => { write!(f, "&{}", if mutbl == Mutability::Mut { "mut " } else { "" })? } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs index d130827a77e8..a0f6b9368ee0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/deconstruct_pat.rs @@ -82,7 +82,7 @@ fn expand<'p>(pat: &'p Pat, vec: &mut Vec<&'p Pat>) { pats } -/// [Constructor] uses this in umimplemented variants. +/// [Constructor] uses this in unimplemented variants. /// It allows porting match expressions from upstream algorithm without losing semantics. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(super) enum Void {} @@ -384,7 +384,7 @@ pub(super) fn arity(&self, pcx: PatCtxt<'_, '_>) -> usize { TyKind::Tuple(arity, ..) => arity, TyKind::Ref(..) => 1, TyKind::Adt(adt, ..) => { - if is_box(adt.0, pcx.cx.db) { + if is_box(pcx.cx.db, adt.0) { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. 1 @@ -772,7 +772,7 @@ fn list_variant_nonhidden_fields<'a>( (0..fields_len).map(|idx| LocalFieldId::from_raw(idx.into())).filter_map(move |fid| { let ty = field_ty[fid].clone().substitute(Interner, substs); - let ty = normalize(cx.db, cx.body, ty); + let ty = normalize(cx.db, cx.db.trait_environment_for_body(cx.body), ty); let is_visible = matches!(adt, hir_def::AdtId::EnumId(..)) || visibility[fid].is_visible_from(cx.db.upcast(), cx.module); let is_uninhabited = cx.is_uninhabited(&ty); @@ -800,7 +800,7 @@ pub(crate) fn wildcards( } TyKind::Ref(.., rty) => Fields::wildcards_from_tys(cx, once(rty.clone())), &TyKind::Adt(AdtId(adt), ref substs) => { - if is_box(adt, cx.db) { + if is_box(cx.db, adt) { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. let subst_ty = substs.at(Interner, 0).assert_ty_ref(Interner).clone(); @@ -905,7 +905,7 @@ pub(crate) fn from_pat(cx: &MatchCheckCtx<'_, 'p>, pat: &Pat) -> Self { } fields = Fields::from_iter(cx, wilds) } - TyKind::Adt(adt, substs) if is_box(adt.0, cx.db) => { + TyKind::Adt(adt, substs) if is_box(cx.db, adt.0) => { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. // FIXME(Nadrieril): A `Box` can in theory be matched either with `Box(_, @@ -992,7 +992,7 @@ pub(crate) fn to_pat(&self, cx: &MatchCheckCtx<'_, 'p>) -> Pat { }) .collect(), }, - TyKind::Adt(adt, _) if is_box(adt.0, cx.db) => { + TyKind::Adt(adt, _) if is_box(cx.db, adt.0) => { // Without `box_patterns`, the only legal pattern of type `Box` is `_` (outside // of `std`). So this branch is only reachable when the feature is enabled and // the pattern is a box pattern. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_util.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_util.rs index b89b4f2bfb7d..217454499ef6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_util.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_util.rs @@ -1,4 +1,4 @@ -//! Pattern untilities. +//! Pattern utilities. //! //! Originates from `rustc_hir::pat_util` diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/usefulness.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/usefulness.rs index c4d709a975b0..d737b24ad32b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/usefulness.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/usefulness.rs @@ -755,7 +755,7 @@ pub(crate) enum Reachability { /// The arm is reachable. This additionally carries a set of or-pattern branches that have been /// found to be unreachable despite the overall arm being reachable. Used only in the presence /// of or-patterns, otherwise it stays empty. - // FIXME: store ureachable subpattern IDs + // FIXME: store unreachable subpattern IDs Reachable, /// The arm is unreachable. Unreachable, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs index d25c0ccf00dc..7c38e6583a75 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -3,7 +3,7 @@ use hir_def::{ body::Body, - expr::{Expr, ExprId, UnaryOp}, + hir::{Expr, ExprId, UnaryOp}, resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, DefWithBodyId, }; @@ -73,7 +73,7 @@ fn walk_unsafe( } Expr::Path(path) => { let resolver = resolver_for_expr(db.upcast(), def, current); - let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path()); + let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path); if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial { if db.static_data(id).mutable { unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block }); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index bd3eccfe43da..f90e025c7cc6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -7,32 +7,36 @@ use base_db::CrateId; use chalk_ir::{BoundVar, TyKind}; use hir_def::{ - adt::VariantData, - body, + data::adt::VariantData, db::DefDatabase, find_path, generics::{TypeOrConstParamData, TypeParamProvenance}, item_scope::ItemInNs, lang_item::{LangItem, LangItemTarget}, + nameres::DefMap, path::{Path, PathKind}, type_ref::{TraitBoundModifier, TypeBound, TypeRef}, visibility::Visibility, - HasModule, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId, + EnumVariantId, HasModule, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, + TraitId, }; use hir_expand::{hygiene::Hygiene, name::Name}; use intern::{Internable, Interned}; use itertools::Itertools; +use la_arena::ArenaMap; use smallvec::SmallVec; +use stdx::never; use crate::{ + consteval::try_const_usize, db::HirDatabase, from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, - layout::layout_of_ty, + layout::Layout, lt_from_placeholder_idx, mapping::from_chalk, mir::pad16, primitive, to_assoc_type_id, - utils::{self, generics}, + utils::{self, detect_variant_from_bytes, generics, ClosureSubst}, AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, @@ -64,6 +68,7 @@ pub struct HirFormatter<'a> { curr_size: usize, pub(crate) max_size: Option, omit_verbose_types: bool, + closure_style: ClosureStyle, display_target: DisplayTarget, } @@ -87,6 +92,7 @@ fn into_displayable<'a>( max_size: Option, omit_verbose_types: bool, display_target: DisplayTarget, + closure_style: ClosureStyle, ) -> HirDisplayWrapper<'a, Self> where Self: Sized, @@ -95,7 +101,14 @@ fn into_displayable<'a>( !matches!(display_target, DisplayTarget::SourceCode { .. }), "HirDisplayWrapper cannot fail with DisplaySourceCodeError, use HirDisplay::hir_fmt directly instead" ); - HirDisplayWrapper { db, t: self, max_size, omit_verbose_types, display_target } + HirDisplayWrapper { + db, + t: self, + max_size, + omit_verbose_types, + display_target, + closure_style, + } } /// Returns a `Display`able type that is human-readable. @@ -109,6 +122,7 @@ fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self> t: self, max_size: None, omit_verbose_types: false, + closure_style: ClosureStyle::ImplFn, display_target: DisplayTarget::Diagnostics, } } @@ -128,6 +142,7 @@ fn display_truncated<'a>( t: self, max_size, omit_verbose_types: true, + closure_style: ClosureStyle::ImplFn, display_target: DisplayTarget::Diagnostics, } } @@ -138,6 +153,7 @@ fn display_source_code<'a>( &'a self, db: &'a dyn HirDatabase, module_id: ModuleId, + allow_opaque: bool, ) -> Result { let mut result = String::new(); match self.hir_fmt(&mut HirFormatter { @@ -147,7 +163,8 @@ fn display_source_code<'a>( curr_size: 0, max_size: None, omit_verbose_types: false, - display_target: DisplayTarget::SourceCode { module_id }, + closure_style: ClosureStyle::ImplFn, + display_target: DisplayTarget::SourceCode { module_id, allow_opaque }, }) { Ok(()) => {} Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"), @@ -166,6 +183,7 @@ fn display_test<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, t: self, max_size: None, omit_verbose_types: false, + closure_style: ClosureStyle::ImplFn, display_target: DisplayTarget::Test, } } @@ -235,26 +253,34 @@ pub enum DisplayTarget { Diagnostics, /// Display types for inserting them in source files. /// The generated code should compile, so paths need to be qualified. - SourceCode { module_id: ModuleId }, + SourceCode { module_id: ModuleId, allow_opaque: bool }, /// Only for test purpose to keep real types Test, } impl DisplayTarget { - fn is_source_code(&self) -> bool { + fn is_source_code(self) -> bool { matches!(self, Self::SourceCode { .. }) } - fn is_test(&self) -> bool { + + fn is_test(self) -> bool { matches!(self, Self::Test) } + + fn allows_opaque(self) -> bool { + match self { + Self::SourceCode { allow_opaque, .. } => allow_opaque, + _ => true, + } + } } #[derive(Debug)] pub enum DisplaySourceCodeError { PathNotFound, UnknownType, - Closure, Generator, + OpaqueType, } pub enum HirDisplayError { @@ -274,9 +300,25 @@ pub struct HirDisplayWrapper<'a, T> { t: &'a T, max_size: Option, omit_verbose_types: bool, + closure_style: ClosureStyle, display_target: DisplayTarget, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ClosureStyle { + /// `impl FnX(i32, i32) -> i32`, where `FnX` is the most special trait between `Fn`, `FnMut`, `FnOnce` that the + /// closure implements. This is the default. + ImplFn, + /// `|i32, i32| -> i32` + RANotation, + /// `{closure#14825}`, useful for some diagnostics (like type mismatch) and internal usage. + ClosureWithId, + /// `{closure#14825}`, useful for internal usage. + ClosureWithSubst, + /// `…`, which is the `TYPE_HINT_TRUNCATION` + Hide, +} + impl HirDisplayWrapper<'_, T> { pub fn write_to(&self, f: &mut F) -> Result<(), HirDisplayError> { self.t.hir_fmt(&mut HirFormatter { @@ -287,8 +329,14 @@ pub fn write_to(&self, f: &mut F) -> Result<(), HirDisplayError> { max_size: self.max_size, omit_verbose_types: self.omit_verbose_types, display_target: self.display_target, + closure_style: self.closure_style, }) } + + pub fn with_closure_style(mut self, c: ClosureStyle) -> Self { + self.closure_style = c; + self + } } impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T> @@ -330,7 +378,13 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { let trait_ref = self.trait_ref(f.db); write!(f, "<")?; fmt_trait_ref(f, &trait_ref, true)?; - write!(f, ">::{}", f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id)).name)?; + write!( + f, + ">::{}", + f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id)) + .name + .display(f.db.upcast()) + )?; let proj_params_count = self.substitution.len(Interner) - trait_ref.substitution.len(Interner); let proj_params = &self.substitution.as_slice(Interner)[..proj_params_count]; @@ -373,10 +427,16 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { let id = from_placeholder_idx(f.db, *idx); let generics = generics(f.db.upcast(), id.parent); let param_data = &generics.params.type_or_consts[id.local_id]; - write!(f, "{}", param_data.name().unwrap()) + write!(f, "{}", param_data.name().unwrap().display(f.db.upcast()))?; + Ok(()) } ConstValue::Concrete(c) => match &c.interned { ConstScalar::Bytes(b, m) => render_const_scalar(f, &b, m, &data.ty), + ConstScalar::UnevaluatedConst(c, parameters) => { + write!(f, "{}", c.name(f.db.upcast()))?; + hir_fmt_generics(f, parameters, c.generic_def(f.db.upcast()))?; + Ok(()) + } ConstScalar::Unknown => f.write_char('_'), }, } @@ -411,8 +471,11 @@ fn render_const_scalar( memory_map: &MemoryMap, ty: &Ty, ) -> Result<(), HirDisplayError> { + // FIXME: We need to get krate from the final callers of the hir display + // infrastructure and have it here as a field on `f`. + let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap(); match ty.kind(Interner) { - chalk_ir::TyKind::Scalar(s) => match s { + TyKind::Scalar(s) => match s { Scalar::Bool => write!(f, "{}", if b[0] == 0 { false } else { true }), Scalar::Char => { let x = u128::from_le_bytes(pad16(b, false)) as u32; @@ -440,22 +503,54 @@ fn render_const_scalar( } }, }, - chalk_ir::TyKind::Ref(_, _, t) => match t.kind(Interner) { - chalk_ir::TyKind::Str => { + TyKind::Ref(_, _, t) => match t.kind(Interner) { + TyKind::Str => { let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap()); - let bytes = memory_map.0.get(&addr).map(|x| &**x).unwrap_or(&[]); - let s = std::str::from_utf8(bytes).unwrap_or(""); + let size = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap()); + let Some(bytes) = memory_map.get(addr, size) else { + return f.write_str(""); + }; + let s = std::str::from_utf8(&bytes).unwrap_or(""); write!(f, "{s:?}") } - _ => f.write_str(""), + TyKind::Slice(ty) => { + let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap()); + let count = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap()); + let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + return f.write_str(""); + }; + let size_one = layout.size.bytes_usize(); + let Some(bytes) = memory_map.get(addr, size_one * count) else { + return f.write_str(""); + }; + f.write_str("&[")?; + let mut first = true; + for i in 0..count { + if first { + first = false; + } else { + f.write_str(", ")?; + } + let offset = size_one * i; + render_const_scalar(f, &bytes[offset..offset + size_one], memory_map, &ty)?; + } + f.write_str("]") + } + _ => { + let addr = usize::from_le_bytes(b.try_into().unwrap()); + let Ok(layout) = f.db.layout_of_ty(t.clone(), krate) else { + return f.write_str(""); + }; + let size = layout.size.bytes_usize(); + let Some(bytes) = memory_map.get(addr, size) else { + return f.write_str(""); + }; + f.write_str("&")?; + render_const_scalar(f, bytes, memory_map, t) + } }, - chalk_ir::TyKind::Tuple(_, subst) => { - // FIXME: Remove this line. If the target data layout is independent - // of the krate, the `db.target_data_layout` and its callers like `layout_of_ty` don't need - // to get krate. Otherwise, we need to get krate from the final callers of the hir display - // infrastructure and have it here as a field on `f`. - let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap(); - let Ok(layout) = layout_of_ty(f.db, ty, krate) else { + TyKind::Tuple(_, subst) => { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { return f.write_str(""); }; f.write_str("(")?; @@ -468,7 +563,7 @@ fn render_const_scalar( } let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument let offset = layout.fields.offset(id).bytes_usize(); - let Ok(layout) = layout_of_ty(f.db, &ty, krate) else { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { f.write_str("")?; continue; }; @@ -477,62 +572,144 @@ fn render_const_scalar( } f.write_str(")") } - chalk_ir::TyKind::Adt(adt, subst) => match adt.0 { - hir_def::AdtId::StructId(s) => { - let data = f.db.struct_data(s); - let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone()) else { - return f.write_str(""); - }; - match data.variant_data.as_ref() { - VariantData::Record(fields) | VariantData::Tuple(fields) => { - let field_types = f.db.field_types(s.into()); - let krate = adt.0.module(f.db.upcast()).krate(); - let render_field = |f: &mut HirFormatter<'_>, id: LocalFieldId| { - let offset = layout - .fields - .offset(u32::from(id.into_raw()) as usize) - .bytes_usize(); - let ty = field_types[id].clone().substitute(Interner, subst); - let Ok(layout) = layout_of_ty(f.db, &ty, krate) else { - return f.write_str(""); - }; - let size = layout.size.bytes_usize(); - render_const_scalar(f, &b[offset..offset + size], memory_map, &ty) - }; - let mut it = fields.iter(); - if matches!(data.variant_data.as_ref(), VariantData::Record(_)) { - write!(f, "{} {{", data.name)?; - if let Some((id, data)) = it.next() { - write!(f, " {}: ", data.name)?; - render_field(f, id)?; - } - for (id, data) in it { - write!(f, ", {}: ", data.name)?; - render_field(f, id)?; - } - write!(f, " }}")?; - } else { - let mut it = it.map(|x| x.0); - write!(f, "{}(", data.name)?; - if let Some(id) = it.next() { - render_field(f, id)?; - } - for id in it { - write!(f, ", ")?; - render_field(f, id)?; - } - write!(f, ")")?; - } - return Ok(()); - } - VariantData::Unit => write!(f, "{}", data.name), + TyKind::Adt(adt, subst) => { + let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone(), krate) else { + return f.write_str(""); + }; + match adt.0 { + hir_def::AdtId::StructId(s) => { + let data = f.db.struct_data(s); + write!(f, "{}", data.name.display(f.db.upcast()))?; + let field_types = f.db.field_types(s.into()); + render_variant_after_name( + &data.variant_data, + f, + &field_types, + adt.0.module(f.db.upcast()).krate(), + &layout, + subst, + b, + memory_map, + ) + } + hir_def::AdtId::UnionId(u) => { + write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast())) + } + hir_def::AdtId::EnumId(e) => { + let Some((var_id, var_layout)) = + detect_variant_from_bytes(&layout, f.db, krate, b, e) else { + return f.write_str(""); + }; + let data = &f.db.enum_data(e).variants[var_id]; + write!(f, "{}", data.name.display(f.db.upcast()))?; + let field_types = + f.db.field_types(EnumVariantId { parent: e, local_id: var_id }.into()); + render_variant_after_name( + &data.variant_data, + f, + &field_types, + adt.0.module(f.db.upcast()).krate(), + &var_layout, + subst, + b, + memory_map, + ) } } - hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name), - hir_def::AdtId::EnumId(_) => f.write_str(""), - }, - chalk_ir::TyKind::FnDef(..) => ty.hir_fmt(f), - _ => f.write_str(""), + } + TyKind::FnDef(..) => ty.hir_fmt(f), + TyKind::Function(_) | TyKind::Raw(_, _) => { + let x = u128::from_le_bytes(pad16(b, false)); + write!(f, "{:#X} as ", x)?; + ty.hir_fmt(f) + } + TyKind::Array(ty, len) => { + let Some(len) = try_const_usize(f.db, len) else { + return f.write_str(""); + }; + let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + return f.write_str(""); + }; + let size_one = layout.size.bytes_usize(); + f.write_str("[")?; + let mut first = true; + for i in 0..len as usize { + if first { + first = false; + } else { + f.write_str(", ")?; + } + let offset = size_one * i; + render_const_scalar(f, &b[offset..offset + size_one], memory_map, &ty)?; + } + f.write_str("]") + } + TyKind::Never => f.write_str("!"), + TyKind::Closure(_, _) => f.write_str(""), + TyKind::Generator(_, _) => f.write_str(""), + TyKind::GeneratorWitness(_, _) => f.write_str(""), + // The below arms are unreachable, since const eval will bail out before here. + TyKind::Foreign(_) => f.write_str(""), + TyKind::Error + | TyKind::Placeholder(_) + | TyKind::Alias(_) + | TyKind::AssociatedType(_, _) + | TyKind::OpaqueType(_, _) + | TyKind::BoundVar(_) + | TyKind::InferenceVar(_, _) => f.write_str(""), + // The below arms are unreachable, since we handled them in ref case. + TyKind::Slice(_) | TyKind::Str | TyKind::Dyn(_) => f.write_str(""), + } +} + +fn render_variant_after_name( + data: &VariantData, + f: &mut HirFormatter<'_>, + field_types: &ArenaMap>, + krate: CrateId, + layout: &Layout, + subst: &Substitution, + b: &[u8], + memory_map: &MemoryMap, +) -> Result<(), HirDisplayError> { + match data { + VariantData::Record(fields) | VariantData::Tuple(fields) => { + let render_field = |f: &mut HirFormatter<'_>, id: LocalFieldId| { + let offset = layout.fields.offset(u32::from(id.into_raw()) as usize).bytes_usize(); + let ty = field_types[id].clone().substitute(Interner, subst); + let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + return f.write_str(""); + }; + let size = layout.size.bytes_usize(); + render_const_scalar(f, &b[offset..offset + size], memory_map, &ty) + }; + let mut it = fields.iter(); + if matches!(data, VariantData::Record(_)) { + write!(f, " {{")?; + if let Some((id, data)) = it.next() { + write!(f, " {}: ", data.name.display(f.db.upcast()))?; + render_field(f, id)?; + } + for (id, data) in it { + write!(f, ", {}: ", data.name.display(f.db.upcast()))?; + render_field(f, id)?; + } + write!(f, " }}")?; + } else { + let mut it = it.map(|x| x.0); + write!(f, "(")?; + if let Some(id) = it.next() { + render_field(f, id)?; + } + for id in it { + write!(f, ", ")?; + render_field(f, id)?; + } + write!(f, ")")?; + } + return Ok(()); + } + VariantData::Unit => Ok(()), } } @@ -689,11 +866,17 @@ fn hir_fmt( let sig = db.callable_item_signature(def).substitute(Interner, parameters); f.start_location_link(def.into()); match def { - CallableDefId::FunctionId(ff) => write!(f, "fn {}", db.function_data(ff).name)?, - CallableDefId::StructId(s) => write!(f, "{}", db.struct_data(s).name)?, - CallableDefId::EnumVariantId(e) => { - write!(f, "{}", db.enum_data(e.parent).variants[e.local_id].name)? + CallableDefId::FunctionId(ff) => { + write!(f, "fn {}", db.function_data(ff).name.display(f.db.upcast()))? } + CallableDefId::StructId(s) => { + write!(f, "{}", db.struct_data(s).name.display(f.db.upcast()))? + } + CallableDefId::EnumVariantId(e) => write!( + f, + "{}", + db.enum_data(e.parent).variants[e.local_id].name.display(f.db.upcast()) + )?, }; f.end_location_link(); if parameters.len(Interner) > 0 { @@ -733,16 +916,16 @@ fn hir_fmt( hir_def::AdtId::UnionId(it) => db.union_data(it).name.clone(), hir_def::AdtId::EnumId(it) => db.enum_data(it).name.clone(), }; - write!(f, "{name}")?; + write!(f, "{}", name.display(f.db.upcast()))?; } - DisplayTarget::SourceCode { module_id } => { + DisplayTarget::SourceCode { module_id, allow_opaque: _ } => { if let Some(path) = find_path::find_path( db.upcast(), ItemInNs::Types((*def_id).into()), module_id, false, ) { - write!(f, "{path}")?; + write!(f, "{}", path.display(f.db.upcast()))?; } else { return Err(HirDisplayError::DisplaySourceCodeError( DisplaySourceCodeError::PathNotFound, @@ -752,82 +935,9 @@ fn hir_fmt( } f.end_location_link(); - if parameters.len(Interner) > 0 { - let parameters_to_write = if f.display_target.is_source_code() - || f.omit_verbose_types() - { - match self - .as_generic_def(db) - .map(|generic_def_id| db.generic_defaults(generic_def_id)) - .filter(|defaults| !defaults.is_empty()) - { - None => parameters.as_slice(Interner), - Some(default_parameters) => { - fn should_show( - parameter: &GenericArg, - default_parameters: &[Binders], - i: usize, - parameters: &Substitution, - ) -> bool { - if parameter.ty(Interner).map(|x| x.kind(Interner)) - == Some(&TyKind::Error) - { - return true; - } - if let Some(ConstValue::Concrete(c)) = parameter - .constant(Interner) - .map(|x| &x.data(Interner).value) - { - if c.interned == ConstScalar::Unknown { - return true; - } - } - let default_parameter = match default_parameters.get(i) { - Some(x) => x, - None => return true, - }; - let actual_default = - default_parameter.clone().substitute(Interner, ¶meters); - parameter != &actual_default - } - let mut default_from = 0; - for (i, parameter) in parameters.iter(Interner).enumerate() { - if should_show(parameter, &default_parameters, i, parameters) { - default_from = i + 1; - } - } - ¶meters.as_slice(Interner)[0..default_from] - } - } - } else { - parameters.as_slice(Interner) - }; - if !parameters_to_write.is_empty() { - write!(f, "<")?; + let generic_def = self.as_generic_def(db); - if f.display_target.is_source_code() { - let mut first = true; - for generic_arg in parameters_to_write { - if !first { - write!(f, ", ")?; - } - first = false; - - if generic_arg.ty(Interner).map(|ty| ty.kind(Interner)) - == Some(&TyKind::Error) - { - write!(f, "_")?; - } else { - generic_arg.hir_fmt(f)?; - } - } - } else { - f.write_joined(parameters_to_write, ", ")?; - } - - write!(f, ">")?; - } - } + hir_fmt_generics(f, parameters, generic_def)?; } TyKind::AssociatedType(assoc_type_id, parameters) => { let type_alias = from_assoc_type_id(*assoc_type_id); @@ -841,12 +951,12 @@ fn should_show( // Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types) if f.display_target.is_test() { f.start_location_link(trait_.into()); - write!(f, "{}", trait_data.name)?; + write!(f, "{}", trait_data.name.display(f.db.upcast()))?; f.end_location_link(); write!(f, "::")?; f.start_location_link(type_alias.into()); - write!(f, "{}", type_alias_data.name)?; + write!(f, "{}", type_alias_data.name.display(f.db.upcast()))?; f.end_location_link(); // Note that the generic args for the associated type come before those for the // trait (including the self type). @@ -869,10 +979,15 @@ fn should_show( let alias = from_foreign_def_id(*type_alias); let type_alias = db.type_alias_data(alias); f.start_location_link(alias.into()); - write!(f, "{}", type_alias.name)?; + write!(f, "{}", type_alias.name.display(f.db.upcast()))?; f.end_location_link(); } TyKind::OpaqueType(opaque_ty_id, parameters) => { + if !f.display_target.allows_opaque() { + return Err(HirDisplayError::DisplaySourceCodeError( + DisplaySourceCodeError::OpaqueType, + )); + } let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into()); match impl_trait_id { ImplTraitId::ReturnTypeImplTrait(func, idx) => { @@ -919,26 +1034,52 @@ fn should_show( } } } - TyKind::Closure(.., substs) => { + TyKind::Closure(id, substs) => { if f.display_target.is_source_code() { - return Err(HirDisplayError::DisplaySourceCodeError( - DisplaySourceCodeError::Closure, - )); + if !f.display_target.allows_opaque() { + return Err(HirDisplayError::DisplaySourceCodeError( + DisplaySourceCodeError::OpaqueType, + )); + } else if f.closure_style != ClosureStyle::ImplFn { + never!("Only `impl Fn` is valid for displaying closures in source code"); + } } - let sig = substs.at(Interner, 0).assert_ty_ref(Interner).callable_sig(db); + match f.closure_style { + ClosureStyle::Hide => return write!(f, "{TYPE_HINT_TRUNCATION}"), + ClosureStyle::ClosureWithId => { + return write!(f, "{{closure#{:?}}}", id.0.as_u32()) + } + ClosureStyle::ClosureWithSubst => { + write!(f, "{{closure#{:?}}}", id.0.as_u32())?; + return hir_fmt_generics(f, substs, None); + } + _ => (), + } + let sig = ClosureSubst(substs).sig_ty().callable_sig(db); if let Some(sig) = sig { + let (def, _) = db.lookup_intern_closure((*id).into()); + let infer = db.infer(def); + let (_, kind) = infer.closure_info(id); + match f.closure_style { + ClosureStyle::ImplFn => write!(f, "impl {kind:?}(")?, + ClosureStyle::RANotation => write!(f, "|")?, + _ => unreachable!(), + } if sig.params().is_empty() { - write!(f, "||")?; } else if f.should_truncate() { - write!(f, "|{TYPE_HINT_TRUNCATION}|")?; + write!(f, "{TYPE_HINT_TRUNCATION}")?; } else { - write!(f, "|")?; f.write_joined(sig.params(), ", ")?; - write!(f, "|")?; }; - - write!(f, " -> ")?; - sig.ret().hir_fmt(f)?; + match f.closure_style { + ClosureStyle::ImplFn => write!(f, ")")?, + ClosureStyle::RANotation => write!(f, "|")?, + _ => unreachable!(), + } + if f.closure_style == ClosureStyle::RANotation || !sig.ret().is_unit() { + write!(f, " -> ")?; + sig.ret().hir_fmt(f)?; + } } else { write!(f, "{{closure}}")?; } @@ -950,7 +1091,11 @@ fn should_show( match param_data { TypeOrConstParamData::TypeParamData(p) => match p.provenance { TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => { - write!(f, "{}", p.name.clone().unwrap_or_else(Name::missing))? + write!( + f, + "{}", + p.name.clone().unwrap_or_else(Name::missing).display(f.db.upcast()) + )? } TypeParamProvenance::ArgumentImplTrait => { let substs = generics.placeholder_subst(db); @@ -979,7 +1124,7 @@ fn should_show( } }, TypeOrConstParamData::ConstParamData(p) => { - write!(f, "{}", p.name)?; + write!(f, "{}", p.name.display(f.db.upcast()))?; } } } @@ -1004,6 +1149,11 @@ fn should_show( } TyKind::Alias(AliasTy::Projection(p_ty)) => p_ty.hir_fmt(f)?, TyKind::Alias(AliasTy::Opaque(opaque_ty)) => { + if !f.display_target.allows_opaque() { + return Err(HirDisplayError::DisplaySourceCodeError( + DisplaySourceCodeError::OpaqueType, + )); + } let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into()); match impl_trait_id { ImplTraitId::ReturnTypeImplTrait(func, idx) => { @@ -1067,6 +1217,88 @@ fn should_show( } } +fn hir_fmt_generics( + f: &mut HirFormatter<'_>, + parameters: &Substitution, + generic_def: Option, +) -> Result<(), HirDisplayError> { + let db = f.db; + let lifetime_args_count = generic_def.map_or(0, |g| db.generic_params(g).lifetimes.len()); + if parameters.len(Interner) + lifetime_args_count > 0 { + let parameters_to_write = if f.display_target.is_source_code() || f.omit_verbose_types() { + match generic_def + .map(|generic_def_id| db.generic_defaults(generic_def_id)) + .filter(|defaults| !defaults.is_empty()) + { + None => parameters.as_slice(Interner), + Some(default_parameters) => { + fn should_show( + parameter: &GenericArg, + default_parameters: &[Binders], + i: usize, + parameters: &Substitution, + ) -> bool { + if parameter.ty(Interner).map(|x| x.kind(Interner)) == Some(&TyKind::Error) + { + return true; + } + if let Some(ConstValue::Concrete(c)) = + parameter.constant(Interner).map(|x| &x.data(Interner).value) + { + if c.interned == ConstScalar::Unknown { + return true; + } + } + let default_parameter = match default_parameters.get(i) { + Some(x) => x, + None => return true, + }; + let actual_default = + default_parameter.clone().substitute(Interner, ¶meters); + parameter != &actual_default + } + let mut default_from = 0; + for (i, parameter) in parameters.iter(Interner).enumerate() { + if should_show(parameter, &default_parameters, i, parameters) { + default_from = i + 1; + } + } + ¶meters.as_slice(Interner)[0..default_from] + } + } + } else { + parameters.as_slice(Interner) + }; + if !parameters_to_write.is_empty() || lifetime_args_count != 0 { + write!(f, "<")?; + let mut first = true; + for _ in 0..lifetime_args_count { + if !first { + write!(f, ", ")?; + } + first = false; + write!(f, "'_")?; + } + for generic_arg in parameters_to_write { + if !first { + write!(f, ", ")?; + } + first = false; + if f.display_target.is_source_code() + && generic_arg.ty(Interner).map(|ty| ty.kind(Interner)) == Some(&TyKind::Error) + { + write!(f, "_")?; + } else { + generic_arg.hir_fmt(f)?; + } + } + + write!(f, ">")?; + } + } + Ok(()) +} + impl HirDisplay for CallableSig { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write!(f, "fn(")?; @@ -1170,7 +1402,7 @@ fn write_bounds_like_dyn_trait( // existential) here, which is the only thing that's // possible in actual Rust, and hence don't print it f.start_location_link(trait_.into()); - write!(f, "{}", f.db.trait_data(trait_).name)?; + write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?; f.end_location_link(); if let [_, params @ ..] = &*trait_ref.substitution.as_slice(Interner) { if is_fn_trait { @@ -1209,7 +1441,7 @@ fn write_bounds_like_dyn_trait( let assoc_ty_id = from_assoc_type_id(proj.associated_ty_id); let type_alias = f.db.type_alias_data(assoc_ty_id); f.start_location_link(assoc_ty_id.into()); - write!(f, "{}", type_alias.name)?; + write!(f, "{}", type_alias.name.display(f.db.upcast()))?; f.end_location_link(); let proj_arg_count = generics(f.db.upcast(), assoc_ty_id.into()).len_self(); @@ -1276,7 +1508,7 @@ fn fmt_trait_ref( } let trait_ = tr.hir_trait_id(); f.start_location_link(trait_.into()); - write!(f, "{}", f.db.trait_data(trait_).name)?; + write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?; f.end_location_link(); if tr.substitution.len(Interner) > 1 { write!(f, "<")?; @@ -1306,7 +1538,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write!(f, ">::",)?; let type_alias = from_assoc_type_id(projection_ty.associated_ty_id); f.start_location_link(type_alias.into()); - write!(f, "{}", f.db.type_alias_data(type_alias).name,)?; + write!(f, "{}", f.db.type_alias_data(type_alias).name.display(f.db.upcast()),)?; f.end_location_link(); write!(f, " = ")?; ty.hir_fmt(f)?; @@ -1344,7 +1576,8 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { let id = lt_from_placeholder_idx(f.db, *idx); let generics = generics(f.db.upcast(), id.parent); let param_data = &generics.params.lifetimes[id.local_id]; - write!(f, "{}", param_data.name) + write!(f, "{}", param_data.name.display(f.db.upcast()))?; + Ok(()) } LifetimeData::Static => write!(f, "'static"), LifetimeData::Erased => Ok(()), @@ -1376,7 +1609,7 @@ pub fn write_visibility( Visibility::Public => write!(f, "pub "), Visibility::Module(vis_id) => { let def_map = module_id.def_map(f.db.upcast()); - let root_module_id = def_map.module_id(def_map.root()); + let root_module_id = def_map.module_id(DefMap::ROOT); if vis_id == module_id { // pub(self) or omitted Ok(()) @@ -1420,7 +1653,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { }; write!(f, "&")?; if let Some(lifetime) = lifetime { - write!(f, "{} ", lifetime.name)?; + write!(f, "{} ", lifetime.name.display(f.db.upcast()))?; } write!(f, "{mutability}")?; inner.hir_fmt(f)?; @@ -1428,7 +1661,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { TypeRef::Array(inner, len) => { write!(f, "[")?; inner.hir_fmt(f)?; - write!(f, "; {len}]")?; + write!(f, "; {}]", len.display(f.db.upcast()))?; } TypeRef::Slice(inner) => { write!(f, "[")?; @@ -1445,7 +1678,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { for index in 0..function_parameters.len() { let (param_name, param_type) = &function_parameters[index]; if let Some(name) = param_name { - write!(f, "{name}: ")?; + write!(f, "{}: ", name.display(f.db.upcast()))?; } param_type.hir_fmt(f)?; @@ -1477,7 +1710,10 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { } TypeRef::Macro(macro_call) => { let macro_call = macro_call.to_node(f.db.upcast()); - let ctx = body::LowerCtx::with_hygiene(f.db.upcast(), &Hygiene::new_unhygienic()); + let ctx = hir_def::lower::LowerCtx::with_hygiene( + f.db.upcast(), + &Hygiene::new_unhygienic(), + ); match macro_call.path() { Some(path) => match Path::from_src(path, &ctx) { Some(path) => path.hir_fmt(f)?, @@ -1503,9 +1739,13 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { } path.hir_fmt(f) } - TypeBound::Lifetime(lifetime) => write!(f, "{}", lifetime.name), + TypeBound::Lifetime(lifetime) => write!(f, "{}", lifetime.name.display(f.db.upcast())), TypeBound::ForLifetime(lifetimes, path) => { - write!(f, "for<{}> ", lifetimes.iter().format(", "))?; + write!( + f, + "for<{}> ", + lifetimes.iter().map(|it| it.display(f.db.upcast())).format(", ") + )?; path.hir_fmt(f) } TypeBound::Error => write!(f, "{{error}}"), @@ -1551,7 +1791,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 { write!(f, "::")?; } - write!(f, "{}", segment.name)?; + write!(f, "{}", segment.name.display(f.db.upcast()))?; if let Some(generic_args) = segment.args_and_bindings { // We should be in type context, so format as `Foo` instead of `Foo::`. // Do we actually format expressions? @@ -1598,7 +1838,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { } else { write!(f, ", ")?; } - write!(f, "{}", binding.name)?; + write!(f, "{}", binding.name.display(f.db.upcast()))?; match &binding.type_ref { Some(ty) => { write!(f, " = ")?; @@ -1621,8 +1861,10 @@ impl HirDisplay for hir_def::path::GenericArg { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match self { hir_def::path::GenericArg::Type(ty) => ty.hir_fmt(f), - hir_def::path::GenericArg::Const(c) => write!(f, "{c}"), - hir_def::path::GenericArg::Lifetime(lifetime) => write!(f, "{}", lifetime.name), + hir_def::path::GenericArg::Const(c) => write!(f, "{}", c.display(f.db.upcast())), + hir_def::path::GenericArg::Lifetime(lifetime) => { + write!(f, "{}", lifetime.name.display(f.db.upcast())) + } } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 7de5b4295fcc..80f32e96ee63 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -13,34 +13,38 @@ //! to certain types. To record this, we use the union-find implementation from //! the `ena` crate, which is extracted from rustc. -use std::ops::Index; -use std::sync::Arc; +use std::{convert::identity, ops::Index}; -use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags}; +use chalk_ir::{ + cast::Cast, fold::TypeFoldable, interner::HasInterner, DebruijnIndex, Mutability, Safety, + Scalar, TyKind, TypeFlags, +}; use either::Either; use hir_def::{ body::Body, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, data::{ConstData, StaticData}, - expr::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, PatId}, + hir::LabelId, + hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, PatId}, lang_item::{LangItem, LangItemTarget}, layout::Integer, - path::Path, + path::{ModPath, Path}, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, type_ref::TypeRef, - AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, - ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId, + AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, ItemContainerId, Lookup, + TraitId, TypeAliasId, VariantId, }; use hir_expand::name::{name, Name}; -use la_arena::ArenaMap; +use la_arena::{ArenaMap, Entry}; use rustc_hash::{FxHashMap, FxHashSet}; -use stdx::always; +use stdx::{always, never}; +use triomphe::Arc; use crate::{ - db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany, - lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Const, DomainGoal, + db::HirDatabase, fold_tys, infer::coerce::CoerceMany, lower::ImplTraitLoweringMode, + static_lifetime, to_assoc_type_id, traits::FnTrait, AliasEq, AliasTy, ClosureId, DomainGoal, GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, RpitId, Substitution, - TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind, + TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, }; // This lint has a false positive here. See the link below for details. @@ -51,12 +55,15 @@ #[allow(unreachable_pub)] pub use unify::could_unify; +pub(crate) use self::closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy}; + pub(crate) mod unify; mod path; mod expr; mod pat; mod coerce; -mod closure; +pub(crate) mod closure; +mod mutability; /// The entry point of type inference. pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc { @@ -99,6 +106,10 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc Arc Ty { - if !ty.data(Interner).flags.intersects(TypeFlags::HAS_PROJECTION) { +pub(crate) fn normalize(db: &dyn HirDatabase, trait_env: Arc, ty: Ty) -> Ty { + // FIXME: TypeFlags::HAS_CT_PROJECTION is not implemented in chalk, so TypeFlags::HAS_PROJECTION only + // works for the type case, so we check array unconditionally. Remove the array part + // when the bug in chalk becomes fixed. + if !ty.data(Interner).flags.intersects(TypeFlags::HAS_PROJECTION) + && !matches!(ty.kind(Interner), TyKind::Array(..)) + { return ty; } - let krate = owner.module(db.upcast()).krate(); - let trait_env = owner - .as_generic_def_id() - .map_or_else(|| Arc::new(TraitEnvironment::empty(krate)), |d| db.trait_environment(d)); let mut table = unify::InferenceTable::new(db, trait_env); let ty_with_vars = table.normalize_associated_types_in(ty); @@ -188,7 +200,7 @@ pub enum InferenceDiagnostic { /// Contains the type the field resolves to field_with_same_name: Option, }, - // FIXME: Make this proper + // FIXME: This should be emitted in body lowering BreakOutsideOfLoop { expr: ExprId, is_break: bool, @@ -203,6 +215,10 @@ pub enum InferenceDiagnostic { call_expr: ExprId, found: Ty, }, + TypedHole { + expr: ExprId, + expected: Ty, + }, } /// A mismatch between an expected and an inferred type. @@ -276,6 +292,13 @@ pub struct Adjustment { pub target: Ty, } +impl Adjustment { + pub fn borrow(m: Mutability, ty: Ty) -> Self { + let ty = TyKind::Ref(m, static_lifetime(), ty).intern(Interner); + Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(m)), target: ty } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Adjust { /// Go from ! to any type. @@ -304,6 +327,13 @@ pub enum AutoBorrow { RawPtr(Mutability), } +impl AutoBorrow { + fn mutability(self) -> Mutability { + let (AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) = self; + m + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum PointerCast { /// Go from a fn-item type to a fn-pointer type. @@ -337,6 +367,10 @@ pub enum PointerCast { } /// The result of type inference: A mapping from expressions and patterns to types. +/// +/// When you add a field that stores types (including `Substitution` and the like), don't forget +/// `resolve_completely()`'ing them in `InferenceContext::resolve_all()`. Inference variables must +/// not appear in the final inference result. #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct InferenceResult { /// For each method call expr, records the function it resolves to. @@ -363,8 +397,11 @@ pub struct InferenceResult { standard_types: InternedStandardTypes, /// Stores the types which were implicitly dereferenced in pattern binding modes. pub pat_adjustments: FxHashMap>, - pub pat_binding_modes: FxHashMap, + pub binding_modes: ArenaMap, pub expr_adjustments: FxHashMap>, + pub(crate) closure_info: FxHashMap, FnTrait)>, + // FIXME: remove this field + pub mutated_bindings_in_closure: FxHashSet, } impl InferenceResult { @@ -401,6 +438,9 @@ pub fn expr_type_mismatches(&self) -> impl Iterator None, }) } + pub fn closure_info(&self, closure: &ClosureId) -> &(Vec, FnTrait) { + self.closure_info.get(closure).unwrap() + } } impl Index for InferenceResult { @@ -435,7 +475,6 @@ pub(crate) struct InferenceContext<'a> { pub(crate) body: &'a Body, pub(crate) resolver: Resolver, table: unify::InferenceTable<'a>, - trait_env: Arc, /// The traits in scope, disregarding block modules. This is used for caching purposes. traits_in_scope: FxHashSet, pub(crate) result: InferenceResult, @@ -453,6 +492,14 @@ pub(crate) struct InferenceContext<'a> { resume_yield_tys: Option<(Ty, Ty)>, diverges: Diverges, breakables: Vec, + + // fields related to closure capture + current_captures: Vec, + current_closure: Option, + /// Stores the list of closure ids that need to be analyzed before this closure. See the + /// comment on `InferenceContext::sort_closures` + closure_dependencies: FxHashMap>, + deferred_closures: FxHashMap, ExprId)>>, } #[derive(Clone, Debug)] @@ -462,7 +509,7 @@ struct BreakableContext { /// The coercion target of the context. coerce: Option, /// The optional label of the context. - label: Option, + label: Option, kind: BreakableKind, } @@ -477,21 +524,21 @@ enum BreakableKind { fn find_breakable<'c>( ctxs: &'c mut [BreakableContext], - label: Option<&name::Name>, + label: Option, ) -> Option<&'c mut BreakableContext> { let mut ctxs = ctxs .iter_mut() .rev() .take_while(|it| matches!(it.kind, BreakableKind::Block | BreakableKind::Loop)); match label { - Some(_) => ctxs.find(|ctx| ctx.label.as_ref() == label), + Some(_) => ctxs.find(|ctx| ctx.label == label), None => ctxs.find(|ctx| matches!(ctx.kind, BreakableKind::Loop)), } } fn find_continuable<'c>( ctxs: &'c mut [BreakableContext], - label: Option<&name::Name>, + label: Option, ) -> Option<&'c mut BreakableContext> { match label { Some(_) => find_breakable(ctxs, label).filter(|it| matches!(it.kind, BreakableKind::Loop)), @@ -506,14 +553,10 @@ fn new( body: &'a Body, resolver: Resolver, ) -> Self { - let krate = owner.module(db.upcast()).krate(); - let trait_env = owner - .as_generic_def_id() - .map_or_else(|| Arc::new(TraitEnvironment::empty(krate)), |d| db.trait_environment(d)); + let trait_env = db.trait_environment_for_body(owner); InferenceContext { result: InferenceResult::default(), - table: unify::InferenceTable::new(db, trait_env.clone()), - trait_env, + table: unify::InferenceTable::new(db, trait_env), return_ty: TyKind::Error.intern(Interner), // set in collect_* calls resume_yield_tys: None, return_coercion: None, @@ -524,6 +567,10 @@ fn new( resolver, diverges: Diverges::Maybe, breakables: Vec::new(), + current_captures: vec![], + current_closure: None, + deferred_closures: FxHashMap::default(), + closure_dependencies: FxHashMap::default(), } } @@ -533,6 +580,30 @@ fn new( // there is no problem in it being `pub(crate)`, remove this comment. pub(crate) fn resolve_all(self) -> InferenceResult { let InferenceContext { mut table, mut result, .. } = self; + // Destructure every single field so whenever new fields are added to `InferenceResult` we + // don't forget to handle them here. + let InferenceResult { + method_resolutions, + field_resolutions: _, + variant_resolutions: _, + assoc_resolutions, + diagnostics, + type_of_expr, + type_of_pat, + type_of_binding, + type_of_rpit, + type_of_for_iterator, + type_mismatches, + standard_types: _, + pat_adjustments, + binding_modes: _, + expr_adjustments, + // Types in `closure_info` have already been `resolve_completely()`'d during + // `InferenceContext::infer_closures()` (in `HirPlace::ty()` specifically), so no need + // to resolve them here. + closure_info: _, + mutated_bindings_in_closure: _, + } = &mut result; table.fallback_if_possible(); @@ -541,62 +612,63 @@ pub(crate) fn resolve_all(self) -> InferenceResult { // make sure diverging type variables are marked as such table.propagate_diverging_flag(); - for ty in result.type_of_expr.values_mut() { + for ty in type_of_expr.values_mut() { *ty = table.resolve_completely(ty.clone()); } - for ty in result.type_of_pat.values_mut() { + for ty in type_of_pat.values_mut() { *ty = table.resolve_completely(ty.clone()); } - for ty in result.type_of_binding.values_mut() { + for ty in type_of_binding.values_mut() { *ty = table.resolve_completely(ty.clone()); } - for ty in result.type_of_rpit.values_mut() { + for ty in type_of_rpit.values_mut() { *ty = table.resolve_completely(ty.clone()); } - for ty in result.type_of_for_iterator.values_mut() { + for ty in type_of_for_iterator.values_mut() { *ty = table.resolve_completely(ty.clone()); } - for mismatch in result.type_mismatches.values_mut() { + for mismatch in type_mismatches.values_mut() { mismatch.expected = table.resolve_completely(mismatch.expected.clone()); mismatch.actual = table.resolve_completely(mismatch.actual.clone()); } - result.diagnostics.retain_mut(|diagnostic| { - if let InferenceDiagnostic::ExpectedFunction { found: ty, .. } - | InferenceDiagnostic::UnresolvedField { receiver: ty, .. } - | InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic - { - *ty = table.resolve_completely(ty.clone()); - // FIXME: Remove this when we are on par with rustc in terms of inference - if ty.contains_unknown() { - return false; - } + diagnostics.retain_mut(|diagnostic| { + use InferenceDiagnostic::*; + match diagnostic { + ExpectedFunction { found: ty, .. } + | UnresolvedField { receiver: ty, .. } + | UnresolvedMethodCall { receiver: ty, .. } => { + *ty = table.resolve_completely(ty.clone()); + // FIXME: Remove this when we are on par with rustc in terms of inference + if ty.contains_unknown() { + return false; + } - if let InferenceDiagnostic::UnresolvedMethodCall { field_with_same_name, .. } = - diagnostic - { - let clear = if let Some(ty) = field_with_same_name { - *ty = table.resolve_completely(ty.clone()); - ty.contains_unknown() - } else { - false - }; - if clear { - *field_with_same_name = None; + if let UnresolvedMethodCall { field_with_same_name, .. } = diagnostic { + if let Some(ty) = field_with_same_name { + *ty = table.resolve_completely(ty.clone()); + if ty.contains_unknown() { + *field_with_same_name = None; + } + } } } + TypedHole { expected: ty, .. } => { + *ty = table.resolve_completely(ty.clone()); + } + _ => (), } true }); - for (_, subst) in result.method_resolutions.values_mut() { + for (_, subst) in method_resolutions.values_mut() { *subst = table.resolve_completely(subst.clone()); } - for (_, subst) in result.assoc_resolutions.values_mut() { + for (_, subst) in assoc_resolutions.values_mut() { *subst = table.resolve_completely(subst.clone()); } - for adjustment in result.expr_adjustments.values_mut().flatten() { + for adjustment in expr_adjustments.values_mut().flatten() { adjustment.target = table.resolve_completely(adjustment.target.clone()); } - for adjustment in result.pat_adjustments.values_mut().flatten() { + for adjustment in pat_adjustments.values_mut().flatten() { *adjustment = table.resolve_completely(adjustment.clone()); } result @@ -615,7 +687,7 @@ fn collect_fn(&mut self, func: FunctionId) { let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver) .with_impl_trait_mode(ImplTraitLoweringMode::Param); let mut param_tys = - data.params.iter().map(|(_, type_ref)| ctx.lower_ty(type_ref)).collect::>(); + data.params.iter().map(|type_ref| ctx.lower_ty(type_ref)).collect::>(); // Check if function contains a va_list, if it does then we append it to the parameter types // that are collected from the function data if data.is_varargs() { @@ -634,12 +706,7 @@ fn collect_fn(&mut self, func: FunctionId) { self.infer_top_pat(*pat, &ty); } - let error_ty = &TypeRef::Error; - let return_ty = if data.has_async_kw() { - data.async_ret_type.as_deref().unwrap_or(error_ty) - } else { - &*data.ret_type - }; + let return_ty = &*data.ret_type; let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver) .with_impl_trait_mode(ImplTraitLoweringMode::Opaque); @@ -649,36 +716,16 @@ fn collect_fn(&mut self, func: FunctionId) { let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) { // RPIT opaque types use substitution of their parent function. let fn_placeholders = TyBuilder::placeholder_subst(self.db, func); - fold_tys( - return_ty, - |ty, _| { - let opaque_ty_id = match ty.kind(Interner) { - TyKind::OpaqueType(opaque_ty_id, _) => *opaque_ty_id, - _ => return ty, - }; - let idx = match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) { - ImplTraitId::ReturnTypeImplTrait(_, idx) => idx, - _ => unreachable!(), - }; - let bounds = (*rpits).map_ref(|rpits| { - rpits.impl_traits[idx].bounds.map_ref(|it| it.into_iter()) - }); - let var = self.table.new_type_var(); - let var_subst = Substitution::from1(Interner, var.clone()); - for bound in bounds { - let predicate = - bound.map(|it| it.cloned()).substitute(Interner, &fn_placeholders); - let (var_predicate, binders) = predicate - .substitute(Interner, &var_subst) - .into_value_and_skipped_binders(); - always!(binders.is_empty(Interner)); // quantified where clauses not yet handled - self.push_obligation(var_predicate.cast(Interner)); - } - self.result.type_of_rpit.insert(idx, var.clone()); - var - }, - DebruijnIndex::INNERMOST, - ) + let result = + self.insert_inference_vars_for_rpit(return_ty, rpits.clone(), fn_placeholders); + let rpits = rpits.skip_binders(); + for (id, _) in rpits.impl_traits.iter() { + if let Entry::Vacant(e) = self.result.type_of_rpit.entry(id) { + never!("Missed RPIT in `insert_inference_vars_for_rpit`"); + e.insert(TyKind::Error.intern(Interner)); + } + } + result } else { return_ty }; @@ -687,6 +734,50 @@ fn collect_fn(&mut self, func: FunctionId) { self.return_coercion = Some(CoerceMany::new(self.return_ty.clone())); } + fn insert_inference_vars_for_rpit( + &mut self, + t: T, + rpits: Arc>, + fn_placeholders: Substitution, + ) -> T + where + T: crate::HasInterner + crate::TypeFoldable, + { + fold_tys( + t, + |ty, _| { + let opaque_ty_id = match ty.kind(Interner) { + TyKind::OpaqueType(opaque_ty_id, _) => *opaque_ty_id, + _ => return ty, + }; + let idx = match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) { + ImplTraitId::ReturnTypeImplTrait(_, idx) => idx, + _ => unreachable!(), + }; + let bounds = (*rpits) + .map_ref(|rpits| rpits.impl_traits[idx].bounds.map_ref(|it| it.into_iter())); + let var = self.table.new_type_var(); + let var_subst = Substitution::from1(Interner, var.clone()); + for bound in bounds { + let predicate = + bound.map(|it| it.cloned()).substitute(Interner, &fn_placeholders); + let (var_predicate, binders) = + predicate.substitute(Interner, &var_subst).into_value_and_skipped_binders(); + always!(binders.is_empty(Interner)); // quantified where clauses not yet handled + let var_predicate = self.insert_inference_vars_for_rpit( + var_predicate, + rpits.clone(), + fn_placeholders.clone(), + ); + self.push_obligation(var_predicate.cast(Interner)); + } + self.result.type_of_rpit.insert(idx, var.clone()); + var + }, + DebruijnIndex::INNERMOST, + ) + } + fn infer_body(&mut self) { match self.return_coercion { Some(_) => self.infer_return(self.body.body_expr), @@ -742,43 +833,16 @@ fn err_ty(&self) -> Ty { self.result.standard_types.unknown.clone() } - /// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it. - fn insert_const_vars_shallow(&mut self, c: Const) -> Const { - let data = c.data(Interner); - match &data.value { - ConstValue::Concrete(cc) => match cc.interned { - crate::ConstScalar::Unknown => self.table.new_const_var(data.ty.clone()), - _ => c, - }, - _ => c, - } - } - /// Replaces `Ty::Error` by a new type var, so we can maybe still infer it. fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { - match ty.kind(Interner) { - TyKind::Error => self.table.new_type_var(), - TyKind::InferenceVar(..) => { - let ty_resolved = self.resolve_ty_shallow(&ty); - if ty_resolved.is_unknown() { - self.table.new_type_var() - } else { - ty - } - } - _ => ty, - } + self.table.insert_type_vars_shallow(ty) } - fn insert_type_vars(&mut self, ty: Ty) -> Ty { - fold_tys_and_consts( - ty, - |x, _| match x { - Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)), - Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)), - }, - DebruijnIndex::INNERMOST, - ) + fn insert_type_vars(&mut self, ty: T) -> T + where + T: HasInterner + TypeFoldable, + { + self.table.insert_type_vars(ty) } fn push_obligation(&mut self, o: DomainGoal) { @@ -789,13 +853,75 @@ fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool { self.table.unify(ty1, ty2) } + /// Attempts to returns the deeply last field of nested structures, but + /// does not apply any normalization in its search. Returns the same type + /// if input `ty` is not a structure at all. + fn struct_tail_without_normalization(&mut self, ty: Ty) -> Ty { + self.struct_tail_with_normalize(ty, identity) + } + + /// Returns the deeply last field of nested structures, or the same type if + /// not a structure at all. Corresponds to the only possible unsized field, + /// and its type can be used to determine unsizing strategy. + /// + /// This is parameterized over the normalization strategy (i.e. how to + /// handle `::Assoc` and `impl Trait`); pass the identity + /// function to indicate no normalization should take place. + fn struct_tail_with_normalize( + &mut self, + mut ty: Ty, + mut normalize: impl FnMut(Ty) -> Ty, + ) -> Ty { + // FIXME: fetch the limit properly + let recursion_limit = 10; + for iteration in 0.. { + if iteration > recursion_limit { + return self.err_ty(); + } + match ty.kind(Interner) { + TyKind::Adt(chalk_ir::AdtId(hir_def::AdtId::StructId(struct_id)), substs) => { + match self.db.field_types((*struct_id).into()).values().next_back().cloned() { + Some(field) => { + ty = field.substitute(Interner, substs); + } + None => break, + } + } + TyKind::Adt(..) => break, + TyKind::Tuple(_, substs) => { + match substs + .as_slice(Interner) + .split_last() + .and_then(|(last_ty, _)| last_ty.ty(Interner)) + { + Some(last_ty) => ty = last_ty.clone(), + None => break, + } + } + TyKind::Alias(..) => { + let normalized = normalize(ty.clone()); + if ty == normalized { + return ty; + } else { + ty = normalized; + } + } + _ => break, + } + } + ty + } + /// Recurses through the given type, normalizing associated types mentioned /// in it by replacing them by type variables and registering obligations to /// resolve later. This should be done once for every type we get from some /// type annotation (e.g. from a let type annotation, field type or function /// call). `make_ty` handles this already, but e.g. for field types we need /// to do it as well. - fn normalize_associated_types_in(&mut self, ty: Ty) -> Ty { + fn normalize_associated_types_in(&mut self, ty: T) -> T + where + T: HasInterner + TypeFoldable, + { self.table.normalize_associated_types_in(ty) } @@ -848,10 +974,8 @@ fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Optio None => return (self.err_ty(), None), }; let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); - // FIXME: this should resolve assoc items as well, see this example: - // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 let (resolution, unresolved) = if value_ns { - match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path) { Some(ResolveValueResult::ValueNs(value)) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); @@ -872,11 +996,15 @@ fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Optio None => return (self.err_ty(), None), } } else { - match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { Some(it) => it, None => return (self.err_ty(), None), } }; + let Some(mod_path) = path.mod_path() else { + never!("resolver should always resolve lang item paths"); + return (self.err_ty(), None); + }; return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = ctx.substs_from_path(path, strukt.into(), true); @@ -899,8 +1027,68 @@ fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Optio TypeNs::SelfType(impl_id) => { let generics = crate::utils::generics(self.db.upcast(), impl_id.into()); let substs = generics.placeholder_subst(self.db); - let ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs); - self.resolve_variant_on_alias(ty, unresolved, path) + let mut ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs); + + let Some(mut remaining_idx) = unresolved else { + return self.resolve_variant_on_alias(ty, None, mod_path); + }; + + let mut remaining_segments = path.segments().skip(remaining_idx); + + // We need to try resolving unresolved segments one by one because each may resolve + // to a projection, which `TyLoweringContext` cannot handle on its own. + while !remaining_segments.is_empty() { + let resolved_segment = path.segments().get(remaining_idx - 1).unwrap(); + let current_segment = remaining_segments.take(1); + + // If we can resolve to an enum variant, it takes priority over associated type + // of the same name. + if let Some((AdtId::EnumId(id), _)) = ty.as_adt() { + let enum_data = self.db.enum_data(id); + let name = current_segment.first().unwrap().name; + if let Some(local_id) = enum_data.variant(name) { + let variant = EnumVariantId { parent: id, local_id }; + return if remaining_segments.len() == 1 { + (ty, Some(variant.into())) + } else { + // We still have unresolved paths, but enum variants never have + // associated types! + (self.err_ty(), None) + }; + } + } + + // `lower_partly_resolved_path()` returns `None` as type namespace unless + // `remaining_segments` is empty, which is never the case here. We don't know + // which namespace the new `ty` is in until normalized anyway. + (ty, _) = ctx.lower_partly_resolved_path( + resolution, + resolved_segment, + current_segment, + false, + ); + + ty = self.table.insert_type_vars(ty); + ty = self.table.normalize_associated_types_in(ty); + ty = self.table.resolve_ty_shallow(&ty); + if ty.is_unknown() { + return (self.err_ty(), None); + } + + // FIXME(inherent_associated_types): update `resolution` based on `ty` here. + remaining_idx += 1; + remaining_segments = remaining_segments.skip(1); + } + + let variant = ty.as_adt().and_then(|(id, _)| match id { + AdtId::StructId(s) => Some(VariantId::StructId(s)), + AdtId::UnionId(u) => Some(VariantId::UnionId(u)), + AdtId::EnumId(_) => { + // FIXME Error E0071, expected struct, variant or union type, found enum `Foo` + None + } + }); + (ty, variant) } TypeNs::TypeAliasId(it) => { let container = it.lookup(self.db.upcast()).container; @@ -917,7 +1105,7 @@ fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Optio 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) + self.resolve_variant_on_alias(ty, unresolved, mod_path) } TypeNs::AdtSelfType(_) => { // FIXME this could happen in array size expressions, once we're checking them @@ -953,9 +1141,9 @@ fn resolve_variant_on_alias( &mut self, ty: Ty, unresolved: Option, - path: &Path, + path: &ModPath, ) -> (Ty, Option) { - let remaining = unresolved.map(|x| path.segments().skip(x).len()).filter(|x| x > &0); + let remaining = unresolved.map(|x| path.segments()[x..].len()).filter(|x| x > &0); match remaining { None => { let variant = ty.as_adt().and_then(|(adt_id, _)| match adt_id { @@ -969,7 +1157,7 @@ fn resolve_variant_on_alias( (ty, variant) } Some(1) => { - let segment = path.mod_path().segments().last().unwrap(); + let segment = path.segments().last().unwrap(); // this could be an enum variant or associated type if let Some((AdtId::EnumId(enum_id), _)) = ty.as_adt() { let enum_data = self.db.enum_data(enum_id); @@ -993,22 +1181,6 @@ fn resolve_lang_item(&self, item: LangItem) -> Option { self.db.lang_item(krate, item) } - fn resolve_into_iter_item(&self) -> Option { - let ItemContainerId::TraitId(trait_) = self.resolve_lang_item(LangItem::IntoIterIntoIter)? - .as_function()? - .lookup(self.db.upcast()).container - else { return None }; - self.db.trait_data(trait_).associated_type_by_name(&name![IntoIter]) - } - - fn resolve_iterator_item(&self) -> Option { - let ItemContainerId::TraitId(trait_) = self.resolve_lang_item(LangItem::IteratorNext)? - .as_function()? - .lookup(self.db.upcast()).container - else { return None }; - self.db.trait_data(trait_).associated_type_by_name(&name![Item]) - } - fn resolve_output_on(&self, trait_: TraitId) -> Option { self.db.trait_data(trait_).associated_type_by_name(&name![Output]) } @@ -1017,10 +1189,6 @@ fn resolve_lang_trait(&self, lang: LangItem) -> Option { self.resolve_lang_item(lang)?.as_trait() } - fn resolve_ops_try_output(&self) -> Option { - self.resolve_output_on(self.resolve_lang_trait(LangItem::Try)?) - } - fn resolve_ops_neg_output(&self) -> Option { self.resolve_output_on(self.resolve_lang_trait(LangItem::Neg)?) } @@ -1136,9 +1304,8 @@ fn has_type(ty: Ty) -> Self { /// which still is useful, because it informs integer literals and the like. /// See the test case `test/ui/coerce-expect-unsized.rs` and #20169 /// for examples of where this comes up,. - fn rvalue_hint(table: &mut unify::InferenceTable<'_>, ty: Ty) -> Self { - // FIXME: do struct_tail_without_normalization - match table.resolve_ty_shallow(&ty).kind(Interner) { + fn rvalue_hint(ctx: &mut InferenceContext<'_>, ty: Ty) -> Self { + match ctx.struct_tail_without_normalization(ty.clone()).kind(Interner) { TyKind::Slice(_) | TyKind::Str | TyKind::Dyn(_) => Expectation::RValueLikeUnsized(ty), _ => Expectation::has_type(ty), } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index a6449d019ff6..23189f383e0e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -1,12 +1,33 @@ //! Inference of closure parameter types based on the closure's expected type. -use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, WhereClause}; -use hir_def::{expr::ExprId, HasModule}; +use std::{cmp, collections::HashMap, convert::Infallible, mem}; + +use chalk_ir::{ + cast::Cast, + fold::{FallibleTypeFolder, TypeFoldable}, + AliasEq, AliasTy, BoundVar, DebruijnIndex, FnSubst, Mutability, TyKind, WhereClause, +}; +use hir_def::{ + data::adt::VariantData, + hir::{Array, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat, PatId, Statement, UnaryOp}, + lang_item::LangItem, + resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, + DefWithBodyId, FieldId, HasModule, VariantId, +}; +use hir_expand::name; +use rustc_hash::FxHashMap; use smallvec::SmallVec; +use stdx::never; use crate::{ - to_chalk_trait_id, utils, ChalkTraitId, DynTy, FnPointer, FnSig, Interner, Substitution, Ty, - TyExt, TyKind, + db::HirDatabase, + from_placeholder_idx, make_binders, + mir::{BorrowKind, MirSpan, ProjectionElem}, + static_lifetime, to_chalk_trait_id, + traits::FnTrait, + utils::{self, generics, Generics}, + Adjust, Adjustment, Binders, BindingMode, ChalkTraitId, ClosureId, DynTy, FnPointer, FnSig, + Interner, Substitution, Ty, TyExt, }; use super::{Expectation, InferenceContext}; @@ -86,3 +107,906 @@ fn deduce_sig_from_dyn_ty(&self, dyn_ty: &DynTy) -> Option { None } } + +// The below functions handle capture and closure kind (Fn, FnMut, ..) + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct HirPlace { + pub(crate) local: BindingId, + pub(crate) projections: Vec>, +} + +impl HirPlace { + fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty { + let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone()); + for p in &self.projections { + ty = p.projected_ty( + ty, + ctx.db, + |_, _, _| { + unreachable!("Closure field only happens in MIR"); + }, + ctx.owner.module(ctx.db.upcast()).krate(), + ); + } + ty.clone() + } + + fn capture_kind_of_truncated_place( + &self, + mut current_capture: CaptureKind, + len: usize, + ) -> CaptureKind { + match current_capture { + CaptureKind::ByRef(BorrowKind::Mut { .. }) => { + if self.projections[len..].iter().any(|x| *x == ProjectionElem::Deref) { + current_capture = CaptureKind::ByRef(BorrowKind::Unique); + } + } + _ => (), + } + current_capture + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum CaptureKind { + ByRef(BorrowKind), + ByValue, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CapturedItem { + pub(crate) place: HirPlace, + pub(crate) kind: CaptureKind, + pub(crate) span: MirSpan, + pub(crate) ty: Binders, +} + +impl CapturedItem { + pub fn local(&self) -> BindingId { + self.place.local + } + + pub fn ty(&self, subst: &Substitution) -> Ty { + self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst()) + } + + pub fn kind(&self) -> CaptureKind { + self.kind + } + + pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String { + let body = db.body(owner); + let mut result = body[self.place.local].name.display(db.upcast()).to_string(); + let mut field_need_paren = false; + for proj in &self.place.projections { + match proj { + ProjectionElem::Deref => { + result = format!("*{result}"); + field_need_paren = true; + } + ProjectionElem::Field(f) => { + if field_need_paren { + result = format!("({result})"); + } + let variant_data = f.parent.variant_data(db.upcast()); + let field = match &*variant_data { + VariantData::Record(fields) => fields[f.local_id] + .name + .as_str() + .unwrap_or("[missing field]") + .to_string(), + VariantData::Tuple(fields) => fields + .iter() + .position(|x| x.0 == f.local_id) + .unwrap_or_default() + .to_string(), + VariantData::Unit => "[missing field]".to_string(), + }; + result = format!("{result}.{field}"); + field_need_paren = false; + } + &ProjectionElem::TupleOrClosureField(field) => { + if field_need_paren { + result = format!("({result})"); + } + result = format!("{result}.{field}"); + field_need_paren = false; + } + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::OpaqueCast(_) => { + never!("Not happen in closure capture"); + continue; + } + } + } + result + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct CapturedItemWithoutTy { + pub(crate) place: HirPlace, + pub(crate) kind: CaptureKind, + pub(crate) span: MirSpan, +} + +impl CapturedItemWithoutTy { + fn with_ty(self, ctx: &mut InferenceContext<'_>) -> CapturedItem { + let ty = self.place.ty(ctx).clone(); + let ty = match &self.kind { + CaptureKind::ByValue => ty, + CaptureKind::ByRef(bk) => { + let m = match bk { + BorrowKind::Mut { .. } => Mutability::Mut, + _ => Mutability::Not, + }; + TyKind::Ref(m, static_lifetime(), ty).intern(Interner) + } + }; + return CapturedItem { + place: self.place, + kind: self.kind, + span: self.span, + ty: replace_placeholder_with_binder(ctx.db, ctx.owner, ty), + }; + + fn replace_placeholder_with_binder( + db: &dyn HirDatabase, + owner: DefWithBodyId, + ty: Ty, + ) -> Binders { + struct Filler<'a> { + db: &'a dyn HirDatabase, + generics: Generics, + } + impl FallibleTypeFolder for Filler<'_> { + type Error = (); + + fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn try_fold_free_placeholder_const( + &mut self, + ty: chalk_ir::Ty, + idx: chalk_ir::PlaceholderIndex, + outer_binder: DebruijnIndex, + ) -> Result, Self::Error> { + let x = from_placeholder_idx(self.db, idx); + let Some(idx) = self.generics.param_idx(x) else { + return Err(()); + }; + Ok(BoundVar::new(outer_binder, idx).to_const(Interner, ty)) + } + + fn try_fold_free_placeholder_ty( + &mut self, + idx: chalk_ir::PlaceholderIndex, + outer_binder: DebruijnIndex, + ) -> std::result::Result { + let x = from_placeholder_idx(self.db, idx); + let Some(idx) = self.generics.param_idx(x) else { + return Err(()); + }; + Ok(BoundVar::new(outer_binder, idx).to_ty(Interner)) + } + } + let Some(generic_def) = owner.as_generic_def_id() else { + return Binders::empty(Interner, ty); + }; + let filler = &mut Filler { db, generics: generics(db.upcast(), generic_def) }; + let result = ty.clone().try_fold_with(filler, DebruijnIndex::INNERMOST).unwrap_or(ty); + make_binders(db, &filler.generics, result) + } + } +} + +impl InferenceContext<'_> { + fn place_of_expr(&mut self, tgt_expr: ExprId) -> Option { + let r = self.place_of_expr_without_adjust(tgt_expr)?; + let default = vec![]; + let adjustments = self.result.expr_adjustments.get(&tgt_expr).unwrap_or(&default); + apply_adjusts_to_place(r, adjustments) + } + + fn place_of_expr_without_adjust(&mut self, tgt_expr: ExprId) -> Option { + match &self.body[tgt_expr] { + Expr::Path(p) => { + let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); + if let Some(r) = resolver.resolve_path_in_value_ns(self.db.upcast(), p) { + if let ResolveValueResult::ValueNs(v) = r { + if let ValueNs::LocalBinding(b) = v { + return Some(HirPlace { local: b, projections: vec![] }); + } + } + } + } + Expr::Field { expr, name } => { + let mut place = self.place_of_expr(*expr)?; + if let TyKind::Tuple(..) = self.expr_ty(*expr).kind(Interner) { + let index = name.as_tuple_index()?; + place.projections.push(ProjectionElem::TupleOrClosureField(index)) + } else { + let field = self.result.field_resolution(tgt_expr)?; + place.projections.push(ProjectionElem::Field(field)); + } + return Some(place); + } + Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + if matches!( + self.expr_ty_after_adjustments(*expr).kind(Interner), + TyKind::Ref(..) | TyKind::Raw(..) + ) { + let mut place = self.place_of_expr(*expr)?; + place.projections.push(ProjectionElem::Deref); + return Some(place); + } + } + _ => (), + } + None + } + + fn push_capture(&mut self, capture: CapturedItemWithoutTy) { + self.current_captures.push(capture); + } + + fn ref_expr(&mut self, expr: ExprId) { + if let Some(place) = self.place_of_expr(expr) { + self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared), expr.into()); + } + self.walk_expr(expr); + } + + fn add_capture(&mut self, place: HirPlace, kind: CaptureKind, span: MirSpan) { + if self.is_upvar(&place) { + self.push_capture(CapturedItemWithoutTy { place, kind, span }); + } + } + + fn mutate_expr(&mut self, expr: ExprId) { + if let Some(place) = self.place_of_expr(expr) { + self.add_capture( + place, + CaptureKind::ByRef(BorrowKind::Mut { allow_two_phase_borrow: false }), + expr.into(), + ); + } + self.walk_expr(expr); + } + + fn consume_expr(&mut self, expr: ExprId) { + if let Some(place) = self.place_of_expr(expr) { + self.consume_place(place, expr.into()); + } + self.walk_expr(expr); + } + + fn consume_place(&mut self, place: HirPlace, span: MirSpan) { + if self.is_upvar(&place) { + let ty = place.ty(self).clone(); + let kind = if self.is_ty_copy(ty) { + CaptureKind::ByRef(BorrowKind::Shared) + } else { + CaptureKind::ByValue + }; + self.push_capture(CapturedItemWithoutTy { place, kind, span }); + } + } + + fn walk_expr_with_adjust(&mut self, tgt_expr: ExprId, adjustment: &[Adjustment]) { + if let Some((last, rest)) = adjustment.split_last() { + match last.kind { + Adjust::NeverToAny | Adjust::Deref(None) | Adjust::Pointer(_) => { + self.walk_expr_with_adjust(tgt_expr, rest) + } + Adjust::Deref(Some(m)) => match m.0 { + Some(m) => { + self.ref_capture_with_adjusts(m, tgt_expr, rest); + } + None => unreachable!(), + }, + Adjust::Borrow(b) => { + self.ref_capture_with_adjusts(b.mutability(), tgt_expr, rest); + } + } + } else { + self.walk_expr_without_adjust(tgt_expr); + } + } + + fn ref_capture_with_adjusts(&mut self, m: Mutability, tgt_expr: ExprId, rest: &[Adjustment]) { + let capture_kind = match m { + Mutability::Mut => { + CaptureKind::ByRef(BorrowKind::Mut { allow_two_phase_borrow: false }) + } + Mutability::Not => CaptureKind::ByRef(BorrowKind::Shared), + }; + if let Some(place) = self.place_of_expr_without_adjust(tgt_expr) { + if let Some(place) = apply_adjusts_to_place(place, rest) { + self.add_capture(place, capture_kind, tgt_expr.into()); + } + } + self.walk_expr_with_adjust(tgt_expr, rest); + } + + fn walk_expr(&mut self, tgt_expr: ExprId) { + if let Some(x) = self.result.expr_adjustments.get_mut(&tgt_expr) { + // FIXME: this take is completely unneeded, and just is here to make borrow checker + // happy. Remove it if you can. + let x_taken = mem::take(x); + self.walk_expr_with_adjust(tgt_expr, &x_taken); + *self.result.expr_adjustments.get_mut(&tgt_expr).unwrap() = x_taken; + } else { + self.walk_expr_without_adjust(tgt_expr); + } + } + + fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) { + match &self.body[tgt_expr] { + Expr::If { condition, then_branch, else_branch } => { + self.consume_expr(*condition); + self.consume_expr(*then_branch); + if let &Some(expr) = else_branch { + self.consume_expr(expr); + } + } + Expr::Async { statements, tail, .. } + | Expr::Unsafe { statements, tail, .. } + | Expr::Block { statements, tail, .. } => { + for s in statements.iter() { + match s { + Statement::Let { pat, type_ref: _, initializer, else_branch } => { + if let Some(else_branch) = else_branch { + self.consume_expr(*else_branch); + if let Some(initializer) = initializer { + self.consume_expr(*initializer); + } + return; + } + if let Some(initializer) = initializer { + self.walk_expr(*initializer); + if let Some(place) = self.place_of_expr(*initializer) { + self.consume_with_pat(place, *pat); + } + } + } + Statement::Expr { expr, has_semi: _ } => { + self.consume_expr(*expr); + } + } + } + if let Some(tail) = tail { + self.consume_expr(*tail); + } + } + Expr::While { condition, body, label: _ } => { + self.consume_expr(*condition); + self.consume_expr(*body); + } + Expr::Call { callee, args, is_assignee_expr: _ } => { + self.consume_expr(*callee); + self.consume_exprs(args.iter().copied()); + } + Expr::MethodCall { receiver, args, .. } => { + self.consume_expr(*receiver); + self.consume_exprs(args.iter().copied()); + } + Expr::Match { expr, arms } => { + for arm in arms.iter() { + self.consume_expr(arm.expr); + if let Some(guard) = arm.guard { + self.consume_expr(guard); + } + } + self.walk_expr(*expr); + if let Some(discr_place) = self.place_of_expr(*expr) { + if self.is_upvar(&discr_place) { + let mut capture_mode = None; + for arm in arms.iter() { + self.walk_pat(&mut capture_mode, arm.pat); + } + if let Some(c) = capture_mode { + self.push_capture(CapturedItemWithoutTy { + place: discr_place, + kind: c, + span: (*expr).into(), + }) + } + } + } + } + Expr::Break { expr, label: _ } + | Expr::Return { expr } + | Expr::Yield { expr } + | Expr::Yeet { expr } => { + if let &Some(expr) = expr { + self.consume_expr(expr); + } + } + Expr::RecordLit { fields, spread, .. } => { + if let &Some(expr) = spread { + self.consume_expr(expr); + } + self.consume_exprs(fields.iter().map(|x| x.expr)); + } + Expr::Field { expr, name: _ } => self.select_from_expr(*expr), + Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + if matches!( + self.expr_ty_after_adjustments(*expr).kind(Interner), + TyKind::Ref(..) | TyKind::Raw(..) + ) { + self.select_from_expr(*expr); + } else if let Some((f, _)) = self.result.method_resolution(tgt_expr) { + let mutability = 'b: { + if let Some(deref_trait) = + self.resolve_lang_item(LangItem::DerefMut).and_then(|x| x.as_trait()) + { + if let Some(deref_fn) = + self.db.trait_data(deref_trait).method_by_name(&name![deref_mut]) + { + break 'b deref_fn == f; + } + } + false + }; + if mutability { + self.mutate_expr(*expr); + } else { + self.ref_expr(*expr); + } + } else { + self.select_from_expr(*expr); + } + } + Expr::UnaryOp { expr, op: _ } + | Expr::Array(Array::Repeat { initializer: expr, repeat: _ }) + | Expr::Await { expr } + | Expr::Loop { body: expr, label: _ } + | Expr::Let { pat: _, expr } + | Expr::Box { expr } + | Expr::Cast { expr, type_ref: _ } => { + self.consume_expr(*expr); + } + Expr::Ref { expr, rawness: _, mutability } => match mutability { + hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr), + hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr), + }, + Expr::BinaryOp { lhs, rhs, op } => { + let Some(op) = op else { + return; + }; + if matches!(op, BinaryOp::Assignment { .. }) { + self.mutate_expr(*lhs); + self.consume_expr(*rhs); + return; + } + self.consume_expr(*lhs); + self.consume_expr(*rhs); + } + Expr::Range { lhs, rhs, range_type: _ } => { + if let &Some(expr) = lhs { + self.consume_expr(expr); + } + if let &Some(expr) = rhs { + self.consume_expr(expr); + } + } + Expr::Index { base, index } => { + self.select_from_expr(*base); + self.consume_expr(*index); + } + Expr::Closure { .. } => { + let ty = self.expr_ty(tgt_expr); + let TyKind::Closure(id, _) = ty.kind(Interner) else { + never!("closure type is always closure"); + return; + }; + let (captures, _) = + self.result.closure_info.get(id).expect( + "We sort closures, so we should always have data for inner closures", + ); + let mut cc = mem::take(&mut self.current_captures); + cc.extend(captures.iter().filter(|x| self.is_upvar(&x.place)).map(|x| { + CapturedItemWithoutTy { place: x.place.clone(), kind: x.kind, span: x.span } + })); + self.current_captures = cc; + } + Expr::Array(Array::ElementList { elements: exprs, is_assignee_expr: _ }) + | Expr::Tuple { exprs, is_assignee_expr: _ } => { + self.consume_exprs(exprs.iter().copied()) + } + Expr::Missing + | Expr::Continue { .. } + | Expr::Path(_) + | Expr::Literal(_) + | Expr::Const(_) + | Expr::Underscore => (), + } + } + + fn walk_pat(&mut self, result: &mut Option, pat: PatId) { + let mut update_result = |ck: CaptureKind| match result { + Some(r) => { + *r = cmp::max(*r, ck); + } + None => *result = Some(ck), + }; + + self.walk_pat_inner( + pat, + &mut update_result, + BorrowKind::Mut { allow_two_phase_borrow: false }, + ); + } + + fn walk_pat_inner( + &mut self, + p: PatId, + update_result: &mut impl FnMut(CaptureKind), + mut for_mut: BorrowKind, + ) { + match &self.body[p] { + Pat::Ref { .. } + | Pat::Box { .. } + | Pat::Missing + | Pat::Wild + | Pat::Tuple { .. } + | Pat::Or(_) => (), + Pat::TupleStruct { .. } | Pat::Record { .. } => { + if let Some(variant) = self.result.variant_resolution_for_pat(p) { + let adt = variant.adt_id(); + let is_multivariant = match adt { + hir_def::AdtId::EnumId(e) => self.db.enum_data(e).variants.len() != 1, + _ => false, + }; + if is_multivariant { + update_result(CaptureKind::ByRef(BorrowKind::Shared)); + } + } + } + Pat::Slice { .. } + | Pat::ConstBlock(_) + | Pat::Path(_) + | Pat::Lit(_) + | Pat::Range { .. } => { + update_result(CaptureKind::ByRef(BorrowKind::Shared)); + } + Pat::Bind { id, .. } => match self.result.binding_modes[*id] { + crate::BindingMode::Move => { + if self.is_ty_copy(self.result.type_of_binding[*id].clone()) { + update_result(CaptureKind::ByRef(BorrowKind::Shared)); + } else { + update_result(CaptureKind::ByValue); + } + } + crate::BindingMode::Ref(r) => match r { + Mutability::Mut => update_result(CaptureKind::ByRef(for_mut)), + Mutability::Not => update_result(CaptureKind::ByRef(BorrowKind::Shared)), + }, + }, + } + if self.result.pat_adjustments.get(&p).map_or(false, |x| !x.is_empty()) { + for_mut = BorrowKind::Unique; + } + self.body.walk_pats_shallow(p, |p| self.walk_pat_inner(p, update_result, for_mut)); + } + + fn expr_ty(&self, expr: ExprId) -> Ty { + self.result[expr].clone() + } + + fn expr_ty_after_adjustments(&self, e: ExprId) -> Ty { + let mut ty = None; + if let Some(x) = self.result.expr_adjustments.get(&e) { + if let Some(x) = x.last() { + ty = Some(x.target.clone()); + } + } + ty.unwrap_or_else(|| self.expr_ty(e)) + } + + fn is_upvar(&self, place: &HirPlace) -> bool { + let b = &self.body[place.local]; + if let Some(c) = self.current_closure { + let (_, root) = self.db.lookup_intern_closure(c.into()); + return b.is_upvar(root); + } + false + } + + fn is_ty_copy(&mut self, ty: Ty) -> bool { + if let TyKind::Closure(id, _) = ty.kind(Interner) { + // FIXME: We handle closure as a special case, since chalk consider every closure as copy. We + // should probably let chalk know which closures are copy, but I don't know how doing it + // without creating query cycles. + return self.result.closure_info.get(id).map(|x| x.1 == FnTrait::Fn).unwrap_or(true); + } + self.table.resolve_completely(ty).is_copy(self.db, self.owner) + } + + fn select_from_expr(&mut self, expr: ExprId) { + self.walk_expr(expr); + } + + fn adjust_for_move_closure(&mut self) { + for capture in &mut self.current_captures { + if let Some(first_deref) = + capture.place.projections.iter().position(|proj| *proj == ProjectionElem::Deref) + { + capture.place.projections.truncate(first_deref); + } + capture.kind = CaptureKind::ByValue; + } + } + + fn minimize_captures(&mut self) { + self.current_captures.sort_by_key(|x| x.place.projections.len()); + let mut hash_map = HashMap::::new(); + let result = mem::take(&mut self.current_captures); + for item in result { + let mut lookup_place = HirPlace { local: item.place.local, projections: vec![] }; + let mut it = item.place.projections.iter(); + let prev_index = loop { + if let Some(k) = hash_map.get(&lookup_place) { + break Some(*k); + } + match it.next() { + Some(x) => lookup_place.projections.push(x.clone()), + None => break None, + } + }; + match prev_index { + Some(p) => { + let len = self.current_captures[p].place.projections.len(); + let kind_after_truncate = + item.place.capture_kind_of_truncated_place(item.kind, len); + self.current_captures[p].kind = + cmp::max(kind_after_truncate, self.current_captures[p].kind); + } + None => { + hash_map.insert(item.place.clone(), self.current_captures.len()); + self.current_captures.push(item); + } + } + } + } + + fn consume_with_pat(&mut self, mut place: HirPlace, pat: PatId) { + let cnt = self.result.pat_adjustments.get(&pat).map(|x| x.len()).unwrap_or_default(); + place.projections = place + .projections + .iter() + .cloned() + .chain((0..cnt).map(|_| ProjectionElem::Deref)) + .collect::>() + .into(); + match &self.body[pat] { + Pat::Missing | Pat::Wild => (), + Pat::Tuple { args, ellipsis } => { + let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len())); + let field_count = match self.result[pat].kind(Interner) { + TyKind::Tuple(_, s) => s.len(Interner), + _ => return, + }; + let fields = 0..field_count; + let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); + for (arg, i) in it { + let mut p = place.clone(); + p.projections.push(ProjectionElem::TupleOrClosureField(i)); + self.consume_with_pat(p, *arg); + } + } + Pat::Or(pats) => { + for pat in pats.iter() { + self.consume_with_pat(place.clone(), *pat); + } + } + Pat::Record { args, .. } => { + let Some(variant) = self.result.variant_resolution_for_pat(pat) else { + return; + }; + match variant { + VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { + self.consume_place(place, pat.into()) + } + VariantId::StructId(s) => { + let vd = &*self.db.struct_data(s).variant_data; + for field_pat in args.iter() { + let arg = field_pat.pat; + let Some(local_id) = vd.field(&field_pat.name) else { + continue; + }; + let mut p = place.clone(); + p.projections.push(ProjectionElem::Field(FieldId { + parent: variant.into(), + local_id, + })); + self.consume_with_pat(p, arg); + } + } + } + } + Pat::Range { .. } + | Pat::Slice { .. } + | Pat::ConstBlock(_) + | Pat::Path(_) + | Pat::Lit(_) => self.consume_place(place, pat.into()), + Pat::Bind { id, subpat: _ } => { + let mode = self.result.binding_modes[*id]; + let capture_kind = match mode { + BindingMode::Move => { + self.consume_place(place, pat.into()); + return; + } + BindingMode::Ref(Mutability::Not) => BorrowKind::Shared, + BindingMode::Ref(Mutability::Mut) => { + BorrowKind::Mut { allow_two_phase_borrow: false } + } + }; + self.add_capture(place, CaptureKind::ByRef(capture_kind), pat.into()); + } + Pat::TupleStruct { path: _, args, ellipsis } => { + let Some(variant) = self.result.variant_resolution_for_pat(pat) else { + return; + }; + match variant { + VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { + self.consume_place(place, pat.into()) + } + VariantId::StructId(s) => { + let vd = &*self.db.struct_data(s).variant_data; + let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len())); + let fields = vd.fields().iter(); + let it = + al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); + for (arg, (i, _)) in it { + let mut p = place.clone(); + p.projections.push(ProjectionElem::Field(FieldId { + parent: variant.into(), + local_id: i, + })); + self.consume_with_pat(p, *arg); + } + } + } + } + Pat::Ref { pat, mutability: _ } => { + place.projections.push(ProjectionElem::Deref); + self.consume_with_pat(place, *pat) + } + Pat::Box { .. } => (), // not supported + } + } + + fn consume_exprs(&mut self, exprs: impl Iterator) { + for expr in exprs { + self.consume_expr(expr); + } + } + + fn closure_kind(&self) -> FnTrait { + let mut r = FnTrait::Fn; + for x in &self.current_captures { + r = cmp::min( + r, + match &x.kind { + CaptureKind::ByRef(BorrowKind::Unique | BorrowKind::Mut { .. }) => { + FnTrait::FnMut + } + CaptureKind::ByRef(BorrowKind::Shallow | BorrowKind::Shared) => FnTrait::Fn, + CaptureKind::ByValue => FnTrait::FnOnce, + }, + ) + } + r + } + + fn analyze_closure(&mut self, closure: ClosureId) -> FnTrait { + let (_, root) = self.db.lookup_intern_closure(closure.into()); + self.current_closure = Some(closure); + let Expr::Closure { body, capture_by, .. } = &self.body[root] else { + unreachable!("Closure expression id is always closure"); + }; + self.consume_expr(*body); + for item in &self.current_captures { + if matches!(item.kind, CaptureKind::ByRef(BorrowKind::Mut { .. })) + && !item.place.projections.contains(&ProjectionElem::Deref) + { + // FIXME: remove the `mutated_bindings_in_closure` completely and add proper fake reads in + // MIR. I didn't do that due duplicate diagnostics. + self.result.mutated_bindings_in_closure.insert(item.place.local); + } + } + // closure_kind should be done before adjust_for_move_closure + let closure_kind = self.closure_kind(); + match capture_by { + CaptureBy::Value => self.adjust_for_move_closure(), + CaptureBy::Ref => (), + } + self.minimize_captures(); + let result = mem::take(&mut self.current_captures); + let captures = result.into_iter().map(|x| x.with_ty(self)).collect::>(); + self.result.closure_info.insert(closure, (captures, closure_kind)); + closure_kind + } + + pub(crate) fn infer_closures(&mut self) { + let deferred_closures = self.sort_closures(); + for (closure, exprs) in deferred_closures.into_iter().rev() { + self.current_captures = vec![]; + let kind = self.analyze_closure(closure); + + for (derefed_callee, callee_ty, params, expr) in exprs { + if let &Expr::Call { callee, .. } = &self.body[expr] { + let mut adjustments = + self.result.expr_adjustments.remove(&callee).unwrap_or_default(); + self.write_fn_trait_method_resolution( + kind, + &derefed_callee, + &mut adjustments, + &callee_ty, + ¶ms, + expr, + ); + self.result.expr_adjustments.insert(callee, adjustments); + } + } + } + } + + /// We want to analyze some closures before others, to have a correct analysis: + /// * We should analyze nested closures before the parent, since the parent should capture some of + /// the things that its children captures. + /// * If a closure calls another closure, we need to analyze the callee, to find out how we should + /// capture it (e.g. by move for FnOnce) + /// + /// These dependencies are collected in the main inference. We do a topological sort in this function. It + /// will consume the `deferred_closures` field and return its content in a sorted vector. + fn sort_closures(&mut self) -> Vec<(ClosureId, Vec<(Ty, Ty, Vec, ExprId)>)> { + let mut deferred_closures = mem::take(&mut self.deferred_closures); + let mut dependents_count: FxHashMap = + deferred_closures.keys().map(|x| (*x, 0)).collect(); + for (_, deps) in &self.closure_dependencies { + for dep in deps { + *dependents_count.entry(*dep).or_default() += 1; + } + } + let mut queue: Vec<_> = + deferred_closures.keys().copied().filter(|x| dependents_count[x] == 0).collect(); + let mut result = vec![]; + while let Some(x) = queue.pop() { + if let Some(d) = deferred_closures.remove(&x) { + result.push((x, d)); + } + for dep in self.closure_dependencies.get(&x).into_iter().flat_map(|x| x.iter()) { + let cnt = dependents_count.get_mut(dep).unwrap(); + *cnt -= 1; + if *cnt == 0 { + queue.push(*dep); + } + } + } + result + } +} + +fn apply_adjusts_to_place(mut r: HirPlace, adjustments: &[Adjustment]) -> Option { + for adj in adjustments { + match &adj.kind { + Adjust::Deref(None) => { + r.projections.push(ProjectionElem::Deref); + } + _ => return None, + } + } + Some(r) +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index 48c91530266d..05a476f632dc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -5,14 +5,15 @@ //! See and //! `rustc_hir_analysis/check/coercion.rs`. -use std::{iter, sync::Arc}; +use std::iter; -use chalk_ir::{cast::Cast, BoundVar, Goal, Mutability, TyVariableKind}; +use chalk_ir::{cast::Cast, BoundVar, Goal, Mutability, TyKind, TyVariableKind}; use hir_def::{ - expr::ExprId, + hir::ExprId, lang_item::{LangItem, LangItemTarget}, }; use stdx::always; +use triomphe::Arc; use crate::{ autoderef::{Autoderef, AutoderefKind}, @@ -21,8 +22,10 @@ Adjust, Adjustment, AutoBorrow, InferOk, InferenceContext, OverloadedDeref, PointerCast, TypeError, TypeMismatch, }, - static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, Guidance, InEnvironment, Interner, - Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, + static_lifetime, + utils::ClosureSubst, + Canonical, DomainGoal, FnPointer, FnSig, Guidance, InEnvironment, Interner, Solution, + Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, }; use super::unify::InferenceTable; @@ -47,15 +50,23 @@ fn success( Ok(InferOk { goals, value: (adj, target) }) } +pub(super) enum CoercionCause { + // FIXME: Make better use of this. Right now things like return and break without a value + // use it to point to themselves, causing us to report a mismatch on those expressions even + // though technically they themselves are `!` + Expr(ExprId), +} + #[derive(Clone, Debug)] pub(super) struct CoerceMany { expected_ty: Ty, final_ty: Option, + expressions: Vec, } impl CoerceMany { pub(super) fn new(expected: Ty) -> Self { - CoerceMany { expected_ty: expected, final_ty: None } + CoerceMany { expected_ty: expected, final_ty: None, expressions: vec![] } } /// Returns the "expected type" with which this coercion was @@ -86,8 +97,12 @@ pub(super) fn complete(self, ctx: &mut InferenceContext<'_>) -> Ty { } } - pub(super) fn coerce_forced_unit(&mut self, ctx: &mut InferenceContext<'_>) { - self.coerce(ctx, None, &ctx.result.standard_types.unit.clone()) + pub(super) fn coerce_forced_unit( + &mut self, + ctx: &mut InferenceContext<'_>, + cause: CoercionCause, + ) { + self.coerce(ctx, None, &ctx.result.standard_types.unit.clone(), cause) } /// Merge two types from different branches, with possible coercion. @@ -102,6 +117,7 @@ pub(super) fn coerce( ctx: &mut InferenceContext<'_>, expr: Option, expr_ty: &Ty, + cause: CoercionCause, ) { let expr_ty = ctx.resolve_ty_shallow(expr_ty); self.expected_ty = ctx.resolve_ty_shallow(&self.expected_ty); @@ -110,6 +126,8 @@ pub(super) fn coerce( // pointers to have a chance at getting a match. See // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) { + (TyKind::FnDef(x, _), TyKind::FnDef(y, _)) if x == y => None, + (TyKind::Closure(x, _), TyKind::Closure(y, _)) if x == y => None, (TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => { // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure, // we should be coercing the closure to a fn pointer of the safety of the FnDef @@ -125,8 +143,15 @@ pub(super) fn coerce( let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty); let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty); if let (Ok(result1), Ok(result2)) = (result1, result2) { - ctx.table.register_infer_ok(result1); - ctx.table.register_infer_ok(result2); + ctx.table.register_infer_ok(InferOk { value: (), goals: result1.goals }); + for &e in &self.expressions { + ctx.write_expr_adj(e, result1.value.0.clone()); + } + ctx.table.register_infer_ok(InferOk { value: (), goals: result2.goals }); + if let Some(expr) = expr { + ctx.write_expr_adj(expr, result2.value.0); + self.expressions.push(expr); + } return self.final_ty = Some(target_ty); } } @@ -140,14 +165,19 @@ pub(super) fn coerce( } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) { self.final_ty = Some(res); } else { - if let Some(id) = expr { - ctx.result.type_mismatches.insert( - id.into(), - TypeMismatch { expected: self.merged_ty().clone(), actual: expr_ty.clone() }, - ); + match cause { + CoercionCause::Expr(id) => { + ctx.result.type_mismatches.insert( + id.into(), + TypeMismatch { expected: self.merged_ty(), actual: expr_ty.clone() }, + ); + } } cov_mark::hit!(coerce_merge_fail_fallback); } + if let Some(expr) = expr { + self.expressions.push(expr); + } } } @@ -625,7 +655,7 @@ fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> CoerceResult { // Need to find out in what cases this is necessary let solution = self .db - .trait_solve(krate, canonicalized.value.clone().cast(Interner)) + .trait_solve(krate, self.trait_env.block, canonicalized.value.clone().cast(Interner)) .ok_or(TypeError)?; match solution { @@ -657,7 +687,7 @@ fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> CoerceResult { } fn coerce_closure_fn_ty(closure_substs: &Substitution, safety: chalk_ir::Safety) -> Ty { - let closure_sig = closure_substs.at(Interner, 0).assert_ty_ref(Interner).clone(); + let closure_sig = ClosureSubst(closure_substs).sig_ty().clone(); match closure_sig.kind(Interner) { TyKind::Function(fn_ty) => TyKind::Function(FnPointer { num_binders: fn_ty.num_binders, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index ee186673ee13..33e98ac86cf6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -6,37 +6,43 @@ }; use chalk_ir::{ - cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyKind, TyVariableKind, + cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind, }; use hir_def::{ - expr::{ + generics::TypeOrConstParamData, + hir::{ ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, LabelId, Literal, Statement, UnaryOp, }, - generics::TypeOrConstParamData, - lang_item::LangItem, + lang_item::{LangItem, LangItemTarget}, path::{GenericArg, GenericArgs}, - ConstParamId, FieldId, ItemContainerId, Lookup, + BlockId, ConstParamId, FieldId, ItemContainerId, Lookup, }; use hir_expand::name::{name, Name}; use stdx::always; use syntax::ast::RangeOp; +use triomphe::Arc; use crate::{ - autoderef::{self, Autoderef}, + autoderef::{builtin_deref, deref_by_trait, Autoderef}, consteval, infer::{ - coerce::CoerceMany, find_continuable, pat::contains_explicit_ref_binding, BreakableKind, + coerce::{CoerceMany, CoercionCause}, + find_continuable, + pat::contains_explicit_ref_binding, + BreakableKind, }, + lang_items::lang_items_for_bin_op, lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, mapping::{from_chalk, ToChalk}, - method_resolution::{self, lang_items_for_bin_op, VisibleFromModule}, + method_resolution::{self, VisibleFromModule}, primitive::{self, UintTy}, static_lifetime, to_chalk_trait_id, + traits::FnTrait, utils::{generics, Generics}, Adjust, Adjustment, AdtId, AutoBorrow, Binders, CallableDefId, FnPointer, FnSig, FnSubst, - Interner, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, + Interner, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind, }; use super::{ @@ -83,10 +89,10 @@ pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) } } - pub(super) fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty { + fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty { let ty = self.infer_expr_inner(expr, expected); // While we don't allow *arbitrary* coercions here, we *do* allow - // coercions from ! to `expected`. + // coercions from `!` to `expected`. if ty.is_never() { if let Some(adjustments) = self.result.expr_adjustments.get(&expr) { return if let [Adjustment { kind: Adjust::NeverToAny, target }] = &**adjustments { @@ -96,13 +102,22 @@ pub(super) fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expect }; } - let adj_ty = self.table.new_type_var(); - self.write_expr_adj( - expr, - vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty.clone() }], - ); - adj_ty + if let Some(target) = expected.only_has_type(&mut self.table) { + self.coerce(Some(expr), &ty, &target) + .expect("never-to-any coercion should always succeed") + } else { + ty + } } else { + if let Some(expected_ty) = expected.only_has_type(&mut self.table) { + let could_unify = self.unify(&ty, &expected_ty); + if !could_unify { + self.result.type_mismatches.insert( + expr.into(), + TypeMismatch { expected: expected_ty, actual: ty.clone() }, + ); + } + } ty } } @@ -120,24 +135,28 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { ); let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); - let mut both_arms_diverge = Diverges::Always; let then_ty = self.infer_expr_inner(then_branch, expected); - both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); + let then_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table)); - coerce.coerce(self, Some(then_branch), &then_ty); + coerce.coerce(self, Some(then_branch), &then_ty, CoercionCause::Expr(then_branch)); match else_branch { Some(else_branch) => { let else_ty = self.infer_expr_inner(else_branch, expected); - coerce.coerce(self, Some(else_branch), &else_ty); + let else_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); + coerce.coerce( + self, + Some(else_branch), + &else_ty, + CoercionCause::Expr(else_branch), + ); + self.diverges = condition_diverges | then_diverges & else_diverges; } None => { - coerce.coerce_forced_unit(self); + coerce.coerce_forced_unit(self, CoercionCause::Expr(tgt_expr)); + self.diverges = condition_diverges; } } - both_arms_diverge &= self.diverges; - - self.diverges = condition_diverges | both_arms_diverge; coerce.complete(self) } @@ -146,67 +165,21 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { self.infer_top_pat(pat, &input_ty); self.result.standard_types.bool_.clone() } - Expr::Block { statements, tail, label, id: _ } => { - self.infer_block(tgt_expr, statements, *tail, *label, expected) + Expr::Block { statements, tail, label, id } => { + self.infer_block(tgt_expr, *id, statements, *tail, *label, expected) } - Expr::Unsafe { id: _, statements, tail } => { - self.infer_block(tgt_expr, statements, *tail, None, expected) + Expr::Unsafe { id, statements, tail } => { + self.infer_block(tgt_expr, *id, statements, *tail, None, expected) } - Expr::Const { id: _, statements, tail } => { + Expr::Const(id) => { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { - this.infer_block(tgt_expr, statements, *tail, None, expected) + let (_, expr) = this.db.lookup_intern_anonymous_const(*id); + this.infer_expr(expr, expected) }) .1 } - Expr::TryBlock { id: _, statements, tail } => { - // The type that is returned from the try block - let try_ty = self.table.new_type_var(); - if let Some(ty) = expected.only_has_type(&mut self.table) { - self.unify(&try_ty, &ty); - } - - // The ok-ish type that is expected from the last expression - let ok_ty = - self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output()); - - self.infer_block( - tgt_expr, - statements, - *tail, - None, - &Expectation::has_type(ok_ty.clone()), - ); - try_ty - } - Expr::Async { id: _, statements, tail } => { - let ret_ty = self.table.new_type_var(); - 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_ret_coercion = - mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); - - let (_, inner_ty) = - self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { - this.infer_block( - tgt_expr, - statements, - *tail, - None, - &Expectation::has_type(ret_ty), - ) - }); - - self.diverges = prev_diverges; - self.return_ty = prev_ret_ty; - self.return_coercion = prev_ret_coercion; - - // Use the first type parameter as the output type of future. - // existential type AsyncBlockImplTrait: Future - let impl_trait_id = - crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, tgt_expr); - let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); - TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty)) - .intern(Interner) + Expr::Async { id, statements, tail } => { + self.infer_async_block(tgt_expr, id, statements, tail) } &Expr::Loop { body, label } => { // FIXME: should be: @@ -238,25 +211,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { self.diverges = Diverges::Maybe; TyBuilder::unit() } - &Expr::For { iterable, body, pat, label } => { - let iterable_ty = self.infer_expr(iterable, &Expectation::none()); - 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.clone(), self.resolve_iterator_item()); - - self.result.type_of_for_iterator.insert(tgt_expr, into_iter_ty); - - self.infer_top_pat(pat, &pat_ty); - self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { - this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); - }); - - // the body may not run, so it diverging doesn't mean we diverge - self.diverges = Diverges::Maybe; - TyBuilder::unit() - } - Expr::Closure { body, args, ret_type, arg_types, closure_kind } => { + Expr::Closure { body, args, ret_type, arg_types, closure_kind, capture_by: _ } => { assert_eq!(args.len(), arg_types.len()); let mut sig_tys = Vec::with_capacity(arg_types.len() + 1); @@ -276,18 +231,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { None => self.table.new_type_var(), }; if let ClosureKind::Async = closure_kind { - // Use the first type parameter as the output type of future. - // existential type AsyncBlockImplTrait: Future - let impl_trait_id = - crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body); - let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); - sig_tys.push( - TyKind::OpaqueType( - opaque_ty_id, - Substitution::from1(Interner, ret_ty.clone()), - ) - .intern(Interner), - ); + sig_tys.push(self.lower_async_block_type_impl_trait(ret_ty.clone(), *body)); } else { sig_tys.push(ret_ty.clone()); } @@ -302,7 +246,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { }) .intern(Interner); - let (ty, resume_yield_tys) = match closure_kind { + let (id, ty, resume_yield_tys) = match closure_kind { ClosureKind::Generator(_) => { // FIXME: report error when there are more than 1 parameter. let resume_ty = match sig_tys.first() { @@ -322,17 +266,20 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { 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))) + (None, generator_ty, Some((resume_ty, yield_ty))) } ClosureKind::Closure | ClosureKind::Async => { 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()), + TyBuilder::subst_for_closure(self.db, self.owner, sig_ty.clone()), ) .intern(Interner); - - (closure_ty, None) + self.deferred_closures.entry(closure_id).or_default(); + if let Some(c) = self.current_closure { + self.closure_dependencies.entry(c).or_default().push(closure_id); + } + (Some(closure_id), closure_ty, None) } }; @@ -348,9 +295,10 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { // FIXME: lift these out into a struct let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); + let prev_closure = mem::replace(&mut self.current_closure, id); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); let prev_ret_coercion = - mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); + mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty))); let prev_resume_yield_tys = mem::replace(&mut self.resume_yield_tys, resume_yield_tys); @@ -361,6 +309,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { self.diverges = prev_diverges; self.return_ty = prev_ret_ty; self.return_coercion = prev_ret_coercion; + self.current_closure = prev_closure; self.resume_yield_tys = prev_resume_yield_tys; ty @@ -385,16 +334,31 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { || res.is_none(); let (param_tys, ret_ty) = match res { Some((func, params, ret_ty)) => { - let adjustments = auto_deref_adjust_steps(&derefs); - // FIXME: Handle call adjustments for Fn/FnMut - self.write_expr_adj(*callee, adjustments); - if let Some((trait_, func)) = func { - let subst = TyBuilder::subst_for_def(self.db, trait_, None) - .push(callee_ty.clone()) - .push(TyBuilder::tuple_with(params.iter().cloned())) - .build(); - self.write_method_resolution(tgt_expr, func, subst.clone()); + let mut adjustments = auto_deref_adjust_steps(&derefs); + if let TyKind::Closure(c, _) = + self.table.resolve_completely(callee_ty.clone()).kind(Interner) + { + if let Some(par) = self.current_closure { + self.closure_dependencies.entry(par).or_default().push(*c); + } + self.deferred_closures.entry(*c).or_default().push(( + derefed_callee.clone(), + callee_ty.clone(), + params.clone(), + tgt_expr, + )); } + if let Some(fn_x) = func { + self.write_fn_trait_method_resolution( + fn_x, + &derefed_callee, + &mut adjustments, + &callee_ty, + ¶ms, + tgt_expr, + ); + } + self.write_expr_adj(*callee, adjustments); (params, ret_ty) } None => { @@ -470,7 +434,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { let arm_ty = self.infer_expr_inner(arm.expr, &expected); all_arms_diverge &= self.diverges; - coerce.coerce(self, Some(arm.expr), &arm_ty); + coerce.coerce(self, Some(arm.expr), &arm_ty, CoercionCause::Expr(arm.expr)); } self.diverges = matchee_diverges | all_arms_diverge; @@ -484,8 +448,8 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { self.resolver.reset_to_guard(g); ty } - Expr::Continue { label } => { - if let None = find_continuable(&mut self.breakables, label.as_ref()) { + &Expr::Continue { label } => { + if let None = find_continuable(&mut self.breakables, label) { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, is_break: false, @@ -494,9 +458,9 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { }; self.result.standard_types.never.clone() } - Expr::Break { expr, label } => { - let val_ty = if let Some(expr) = *expr { - let opt_coerce_to = match find_breakable(&mut self.breakables, label.as_ref()) { + &Expr::Break { expr, label } => { + let val_ty = if let Some(expr) = expr { + let opt_coerce_to = match find_breakable(&mut self.breakables, label) { Some(ctxt) => match &ctxt.coerce { Some(coerce) => coerce.expected_ty(), None => { @@ -515,13 +479,17 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { TyBuilder::unit() }; - match find_breakable(&mut self.breakables, label.as_ref()) { + match find_breakable(&mut self.breakables, label) { Some(ctxt) => match ctxt.coerce.take() { Some(mut coerce) => { - coerce.coerce(self, *expr, &val_ty); + let cause = match expr { + Some(expr) => CoercionCause::Expr(expr), + None => CoercionCause::Expr(tgt_expr), + }; + coerce.coerce(self, expr, &val_ty, cause); // Avoiding borrowck - let ctxt = find_breakable(&mut self.breakables, label.as_ref()) + let ctxt = find_breakable(&mut self.breakables, label) .expect("breakable stack changed during coercion"); ctxt.may_break = true; ctxt.coerce = Some(coerce); @@ -538,7 +506,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { } self.result.standard_types.never.clone() } - &Expr::Return { expr } => self.infer_expr_return(expr), + &Expr::Return { expr } => self.infer_expr_return(tgt_expr, expr), Expr::Yield { expr } => { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { if let Some(expr) = expr { @@ -589,6 +557,9 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { let field_ty = field_def.map_or(self.err_ty(), |it| { field_types[it.local_id].clone().substitute(Interner, &substs) }); + // Field type might have some unknown types + // FIXME: we may want to emit a single type variable for all instance of type fields? + let field_ty = self.insert_type_vars(field_ty); self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); } if let Some(expr) = spread { @@ -601,26 +572,18 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { 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()); - if let Some(trait_) = self.resolve_lang_trait(LangItem::Try) { - if let Some(func) = self.db.trait_data(trait_).method_by_name(&name!(branch)) { - let subst = TyBuilder::subst_for_def(self.db, trait_, None) - .push(inner_ty.clone()) - .build(); - self.write_method_resolution(tgt_expr, func, subst.clone()); - } - let try_output = self.resolve_output_on(trait_); - self.resolve_associated_type(inner_ty, try_output) - } else { - self.err_ty() - } - } Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); // FIXME: propagate the "castable to" expectation - let _inner_ty = self.infer_expr_no_expect(*expr); - // FIXME check the cast... + let inner_ty = self.infer_expr_no_expect(*expr); + match (inner_ty.kind(Interner), cast_ty.kind(Interner)) { + (TyKind::Ref(_, _, inner), TyKind::Raw(_, cast)) => { + // FIXME: record invalid cast diagnostic in case of mismatch + self.unify(inner, cast); + } + // FIXME check the other kinds of cast... + _ => (), + } cast_ty } Expr::Ref { expr, rawness, mutability } => { @@ -638,7 +601,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { // FIXME: record type error - expected reference but found ptr, // which cannot be coerced } - Expectation::rvalue_hint(&mut self.table, Ty::clone(exp_inner)) + Expectation::rvalue_hint(self, Ty::clone(exp_inner)) } else { Expectation::none() }; @@ -656,7 +619,25 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { // FIXME: Note down method resolution her match op { UnaryOp::Deref => { - autoderef::deref(&mut self.table, inner_ty).unwrap_or_else(|| self.err_ty()) + if let Some(deref_trait) = self.resolve_lang_trait(LangItem::Deref) { + if let Some(deref_fn) = + self.db.trait_data(deref_trait).method_by_name(&name![deref]) + { + // FIXME: this is wrong in multiple ways, subst is empty, and we emit it even for builtin deref (note that + // the mutability is not wrong, and will be fixed in `self.infer_mut`). + self.write_method_resolution( + tgt_expr, + deref_fn, + Substitution::empty(Interner), + ); + } + } + if let Some(derefed) = builtin_deref(&mut self.table, &inner_ty, true) { + self.resolve_ty_shallow(derefed) + } else { + deref_by_trait(&mut self.table, inner_ty) + .unwrap_or_else(|| self.err_ty()) + } } UnaryOp::Neg => { match inner_ty.kind(Interner) { @@ -767,14 +748,16 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { let canonicalized = self.canonicalize(base_ty.clone()); let receiver_adjustments = method_resolution::resolve_indexing_op( self.db, - self.trait_env.clone(), + self.table.trait_env.clone(), canonicalized.value, index_trait, ); - let (self_ty, adj) = receiver_adjustments + let (self_ty, mut adj) = receiver_adjustments .map_or((self.err_ty(), Vec::new()), |adj| { adj.apply(&mut self.table, base_ty) }); + // mutability will be fixed up in `InferenceContext::infer_mut`; + adj.push(Adjustment::borrow(Mutability::Not, self_ty.clone())); self.write_expr_adj(*base, adj); if let Some(func) = self.db.trait_data(index_trait).method_by_name(&name!(index)) @@ -783,7 +766,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { .push(self_ty.clone()) .push(index_ty.clone()) .build(); - self.write_method_resolution(tgt_expr, func, substs.clone()); + self.write_method_resolution(tgt_expr, func, substs); } self.resolve_associated_type_with_params( self_ty, @@ -834,6 +817,20 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { let array_type = TyKind::Array(byte_type, len).intern(Interner); TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(Interner) } + Literal::CString(..) => TyKind::Ref( + Mutability::Not, + static_lifetime(), + self.resolve_lang_item(LangItem::CStr) + .and_then(LangItemTarget::as_struct) + .map_or_else( + || self.err_ty(), + |strukt| { + TyKind::Adt(AdtId(strukt.into()), Substitution::empty(Interner)) + .intern(Interner) + }, + ), + ) + .intern(Interner), Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(Interner), Literal::Int(_v, ty) => match ty { Some(int_ty) => { @@ -859,9 +856,15 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { }, Expr::Underscore => { // Underscore expressions may only appear in assignee expressions, - // which are handled by `infer_assignee_expr()`, so any underscore - // expression reaching this branch is an error. - self.err_ty() + // which are handled by `infer_assignee_expr()`. + // Any other underscore expression is an error, we render a specialized diagnostic + // to let the user know what type is expected though. + let expected = expected.to_option(&mut self.table).unwrap_or_else(|| self.err_ty()); + self.push_diagnostic(InferenceDiagnostic::TypedHole { + expr: tgt_expr, + expected: expected.clone(), + }); + expected } }; // use a new type variable if we got unknown here @@ -874,6 +877,88 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { ty } + fn infer_async_block( + &mut self, + tgt_expr: ExprId, + id: &Option, + statements: &[Statement], + tail: &Option, + ) -> Ty { + let ret_ty = self.table.new_type_var(); + 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_ret_coercion = + mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); + + let (_, inner_ty) = self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { + this.infer_block(tgt_expr, *id, statements, *tail, None, &Expectation::has_type(ret_ty)) + }); + + self.diverges = prev_diverges; + self.return_ty = prev_ret_ty; + self.return_coercion = prev_ret_coercion; + + self.lower_async_block_type_impl_trait(inner_ty, tgt_expr) + } + + pub(crate) fn lower_async_block_type_impl_trait( + &mut self, + inner_ty: Ty, + tgt_expr: ExprId, + ) -> Ty { + // Use the first type parameter as the output type of future. + // existential type AsyncBlockImplTrait: Future + let impl_trait_id = crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, tgt_expr); + let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); + TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty)).intern(Interner) + } + + pub(crate) fn write_fn_trait_method_resolution( + &mut self, + fn_x: FnTrait, + derefed_callee: &Ty, + adjustments: &mut Vec, + callee_ty: &Ty, + params: &Vec, + tgt_expr: ExprId, + ) { + match fn_x { + FnTrait::FnOnce => (), + FnTrait::FnMut => { + if let TyKind::Ref(Mutability::Mut, _, inner) = derefed_callee.kind(Interner) { + if adjustments + .last() + .map(|x| matches!(x.kind, Adjust::Borrow(_))) + .unwrap_or(true) + { + // prefer reborrow to move + adjustments + .push(Adjustment { kind: Adjust::Deref(None), target: inner.clone() }); + adjustments.push(Adjustment::borrow(Mutability::Mut, inner.clone())) + } + } else { + adjustments.push(Adjustment::borrow(Mutability::Mut, derefed_callee.clone())); + } + } + FnTrait::Fn => { + if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) { + adjustments.push(Adjustment::borrow(Mutability::Not, derefed_callee.clone())); + } + } + } + let Some(trait_) = fn_x.get_id(self.db, self.table.trait_env.krate) else { + return; + }; + let trait_data = self.db.trait_data(trait_); + if let Some(func) = trait_data.method_by_name(&fn_x.method_name()) { + let subst = TyBuilder::subst_for_def(self.db, trait_, None) + .push(callee_ty.clone()) + .push(TyBuilder::tuple_with(params.iter().cloned())) + .build(); + self.write_method_resolution(tgt_expr, func, subst.clone()); + } + } + fn infer_expr_array( &mut self, array: &Array, @@ -892,10 +977,10 @@ fn infer_expr_array( (elem_ty, consteval::usize_const(self.db, Some(0), krate)) } Array::ElementList { elements, .. } => { - let mut coerce = CoerceMany::new(elem_ty.clone()); + let mut coerce = CoerceMany::new(elem_ty); for &expr in elements.iter() { let cur_elem_ty = self.infer_expr_inner(expr, &expected); - coerce.coerce(self, Some(expr), &cur_elem_ty); + coerce.coerce(self, Some(expr), &cur_elem_ty, CoercionCause::Expr(expr)); } ( coerce.complete(self), @@ -904,12 +989,13 @@ fn infer_expr_array( } &Array::Repeat { initializer, repeat } => { self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone())); - self.infer_expr( - repeat, - &Expectation::HasType( - TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), - ), - ); + let usize = TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner); + match self.body[repeat] { + Expr::Underscore => { + self.write_expr_ty(repeat, usize); + } + _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize)), + } ( elem_ty, @@ -928,7 +1014,8 @@ fn infer_expr_array( ) } }; - + // Try to evaluate unevaluated constant, and insert variable if is not possible. + let len = self.table.insert_const_vars_shallow(len); TyKind::Array(elem_ty, len).intern(Interner) } @@ -940,18 +1027,18 @@ pub(super) fn infer_return(&mut self, expr: ExprId) { .expected_ty(); let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty)); let mut coerce_many = self.return_coercion.take().unwrap(); - coerce_many.coerce(self, Some(expr), &return_expr_ty); + coerce_many.coerce(self, Some(expr), &return_expr_ty, CoercionCause::Expr(expr)); self.return_coercion = Some(coerce_many); } - fn infer_expr_return(&mut self, expr: Option) -> Ty { + fn infer_expr_return(&mut self, ret: ExprId, expr: Option) -> Ty { match self.return_coercion { Some(_) => { if let Some(expr) = expr { self.infer_return(expr); } else { let mut coerce = self.return_coercion.take().unwrap(); - coerce.coerce_forced_unit(self); + coerce.coerce_forced_unit(self, CoercionCause::Expr(ret)); self.return_coercion = Some(coerce); } } @@ -976,7 +1063,7 @@ fn infer_expr_box(&mut self, inner_expr: ExprId, expected: &Expectation) -> Ty { .filter(|(e_adt, _)| e_adt == &box_id) .map(|(_, subts)| { let g = subts.at(Interner, 0); - Expectation::rvalue_hint(table, Ty::clone(g.assert_ty_ref(Interner))) + Expectation::rvalue_hint(self, Ty::clone(g.assert_ty_ref(Interner))) }) .unwrap_or_else(Expectation::none); @@ -1185,6 +1272,7 @@ fn infer_overloadable_binop( fn infer_block( &mut self, expr: ExprId, + block_id: Option, statements: &[Statement], tail: Option, label: Option, @@ -1192,9 +1280,14 @@ fn infer_block( ) -> Ty { let coerce_ty = expected.coercion_target_type(&mut self.table); let g = self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr); + let prev_env = block_id.map(|block_id| { + let prev_env = self.table.trait_env.clone(); + Arc::make_mut(&mut self.table.trait_env).block = Some(block_id); + prev_env + }); let (break_ty, ty) = - self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty.clone()), label, |this| { + self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty), label, |this| { for stmt in statements { match stmt { Statement::Let { pat, type_ref, initializer, else_branch } => { @@ -1280,6 +1373,9 @@ fn infer_block( } }); self.resolver.reset_to_guard(g); + if let Some(prev_env) = prev_env { + self.table.trait_env = prev_env; + } break_ty.unwrap_or(ty) } @@ -1378,7 +1474,7 @@ fn infer_field_access(&mut self, tgt_expr: ExprId, receiver: ExprId, name: &Name method_resolution::lookup_method( self.db, &canonicalized_receiver.value, - self.trait_env.clone(), + self.table.trait_env.clone(), self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), name, @@ -1411,7 +1507,7 @@ fn infer_method_call( let resolved = method_resolution::lookup_method( self.db, &canonicalized_receiver.value, - self.trait_env.clone(), + self.table.trait_env.clone(), self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), method_name, @@ -1562,7 +1658,7 @@ fn check_call_arguments( // the parameter to coerce to the expected type (for example in // `coerce_unsize_expected_type_4`). let param_ty = self.normalize_associated_types_in(param_ty); - let expected = Expectation::rvalue_hint(&mut self.table, expected_ty); + let expected = Expectation::rvalue_hint(self, expected_ty); // infer with the expected type we have... let ty = self.infer_expr_inner(arg, &expected); @@ -1575,9 +1671,10 @@ fn check_call_arguments( } else { param_ty }; - if !coercion_target.is_unknown() - && self.coerce(Some(arg), &ty, &coercion_target).is_err() - { + // The function signature may contain some unknown types, so we need to insert + // type vars here to avoid type mismatch false positive. + let coercion_target = self.insert_type_vars(coercion_target); + if self.coerce(Some(arg), &ty, &coercion_target).is_err() { self.result.type_mismatches.insert( arg.into(), TypeMismatch { expected: coercion_target, actual: ty.clone() }, @@ -1868,7 +1965,6 @@ fn with_breakable_ctx( cb: impl FnOnce(&mut Self) -> T, ) -> (Option, T) { self.breakables.push({ - let label = label.map(|label| self.body[label].name.clone()); BreakableContext { kind, may_break: false, coerce: ty.map(CoerceMany::new), label } }); let res = cb(self); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs new file mode 100644 index 000000000000..447834243908 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -0,0 +1,218 @@ +//! Finds if an expression is an immutable context or a mutable context, which is used in selecting +//! between `Deref` and `DerefMut` or `Index` and `IndexMut` or similar. + +use chalk_ir::Mutability; +use hir_def::{ + hir::{Array, BinaryOp, BindingAnnotation, Expr, ExprId, PatId, Statement, UnaryOp}, + lang_item::LangItem, +}; +use hir_expand::name; + +use crate::{lower::lower_to_chalk_mutability, Adjust, Adjustment, AutoBorrow, OverloadedDeref}; + +use super::InferenceContext; + +impl<'a> InferenceContext<'a> { + pub(crate) fn infer_mut_body(&mut self) { + self.infer_mut_expr(self.body.body_expr, Mutability::Not); + } + + fn infer_mut_expr(&mut self, tgt_expr: ExprId, mut mutability: Mutability) { + if let Some(adjustments) = self.result.expr_adjustments.get_mut(&tgt_expr) { + for adj in adjustments.iter_mut().rev() { + match &mut adj.kind { + Adjust::NeverToAny | Adjust::Deref(None) | Adjust::Pointer(_) => (), + Adjust::Deref(Some(d)) => *d = OverloadedDeref(Some(mutability)), + Adjust::Borrow(b) => match b { + AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m) => mutability = *m, + }, + } + } + } + self.infer_mut_expr_without_adjust(tgt_expr, mutability); + } + + fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) { + match &self.body[tgt_expr] { + Expr::Missing => (), + &Expr::If { condition, then_branch, else_branch } => { + self.infer_mut_expr(condition, Mutability::Not); + self.infer_mut_expr(then_branch, Mutability::Not); + if let Some(else_branch) = else_branch { + self.infer_mut_expr(else_branch, Mutability::Not); + } + } + Expr::Const(id) => { + let (_, expr) = self.db.lookup_intern_anonymous_const(*id); + self.infer_mut_expr(expr, Mutability::Not); + } + Expr::Let { pat, expr } => self.infer_mut_expr(*expr, self.pat_bound_mutability(*pat)), + Expr::Block { id: _, statements, tail, label: _ } + | Expr::Async { id: _, statements, tail } + | Expr::Unsafe { id: _, statements, tail } => { + for st in statements.iter() { + match st { + Statement::Let { pat, type_ref: _, initializer, else_branch } => { + if let Some(i) = initializer { + self.infer_mut_expr(*i, self.pat_bound_mutability(*pat)); + } + if let Some(e) = else_branch { + self.infer_mut_expr(*e, Mutability::Not); + } + } + Statement::Expr { expr, has_semi: _ } => { + self.infer_mut_expr(*expr, Mutability::Not); + } + } + } + if let Some(tail) = tail { + self.infer_mut_expr(*tail, Mutability::Not); + } + } + &Expr::While { condition: c, body, label: _ } => { + self.infer_mut_expr(c, Mutability::Not); + self.infer_mut_expr(body, Mutability::Not); + } + Expr::MethodCall { receiver: x, method_name: _, args, generic_args: _ } + | Expr::Call { callee: x, args, is_assignee_expr: _ } => { + self.infer_mut_not_expr_iter(args.iter().copied().chain(Some(*x))); + } + Expr::Match { expr, arms } => { + let m = self.pat_iter_bound_mutability(arms.iter().map(|x| x.pat)); + self.infer_mut_expr(*expr, m); + for arm in arms.iter() { + self.infer_mut_expr(arm.expr, Mutability::Not); + if let Some(g) = arm.guard { + self.infer_mut_expr(g, Mutability::Not); + } + } + } + Expr::Yield { expr } + | Expr::Yeet { expr } + | Expr::Return { expr } + | Expr::Break { expr, label: _ } => { + if let &Some(expr) = expr { + self.infer_mut_expr(expr, Mutability::Not); + } + } + Expr::RecordLit { path: _, fields, spread, ellipsis: _, is_assignee_expr: _ } => { + self.infer_mut_not_expr_iter(fields.iter().map(|x| x.expr).chain(*spread)) + } + &Expr::Index { base, index } => { + if mutability == Mutability::Mut { + if let Some((f, _)) = self.result.method_resolutions.get_mut(&tgt_expr) { + if let Some(index_trait) = self + .db + .lang_item(self.table.trait_env.krate, LangItem::IndexMut) + .and_then(|l| l.as_trait()) + { + if let Some(index_fn) = + self.db.trait_data(index_trait).method_by_name(&name![index_mut]) + { + *f = index_fn; + let base_adjustments = self + .result + .expr_adjustments + .get_mut(&base) + .and_then(|it| it.last_mut()); + if let Some(Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(mutability)), + .. + }) = base_adjustments + { + *mutability = Mutability::Mut; + } + } + } + } + } + self.infer_mut_expr(base, mutability); + self.infer_mut_expr(index, Mutability::Not); + } + Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + if let Some((f, _)) = self.result.method_resolutions.get_mut(&tgt_expr) { + if mutability == Mutability::Mut { + if let Some(deref_trait) = self + .db + .lang_item(self.table.trait_env.krate, LangItem::DerefMut) + .and_then(|l| l.as_trait()) + { + if let Some(deref_fn) = + self.db.trait_data(deref_trait).method_by_name(&name![deref_mut]) + { + *f = deref_fn; + } + } + } + } + self.infer_mut_expr(*expr, mutability); + } + Expr::Field { expr, name: _ } => { + self.infer_mut_expr(*expr, mutability); + } + Expr::UnaryOp { expr, op: _ } + | Expr::Range { lhs: Some(expr), rhs: None, range_type: _ } + | Expr::Range { rhs: Some(expr), lhs: None, range_type: _ } + | Expr::Await { expr } + | Expr::Box { expr } + | Expr::Loop { body: expr, label: _ } + | Expr::Cast { expr, type_ref: _ } => { + self.infer_mut_expr(*expr, Mutability::Not); + } + Expr::Ref { expr, rawness: _, mutability } => { + let mutability = lower_to_chalk_mutability(*mutability); + self.infer_mut_expr(*expr, mutability); + } + Expr::BinaryOp { lhs, rhs, op: Some(BinaryOp::Assignment { .. }) } => { + self.infer_mut_expr(*lhs, Mutability::Mut); + self.infer_mut_expr(*rhs, Mutability::Not); + } + Expr::Array(Array::Repeat { initializer: lhs, repeat: rhs }) + | Expr::BinaryOp { lhs, rhs, op: _ } + | Expr::Range { lhs: Some(lhs), rhs: Some(rhs), range_type: _ } => { + self.infer_mut_expr(*lhs, Mutability::Not); + self.infer_mut_expr(*rhs, Mutability::Not); + } + Expr::Closure { body, .. } => { + self.infer_mut_expr(*body, Mutability::Not); + } + Expr::Tuple { exprs, is_assignee_expr: _ } + | Expr::Array(Array::ElementList { elements: exprs, is_assignee_expr: _ }) => { + self.infer_mut_not_expr_iter(exprs.iter().copied()); + } + // These don't need any action, as they don't have sub expressions + Expr::Range { lhs: None, rhs: None, range_type: _ } + | Expr::Literal(_) + | Expr::Path(_) + | Expr::Continue { .. } + | Expr::Underscore => (), + } + } + + fn infer_mut_not_expr_iter(&mut self, exprs: impl Iterator) { + for expr in exprs { + self.infer_mut_expr(expr, Mutability::Not); + } + } + + fn pat_iter_bound_mutability(&self, mut pat: impl Iterator) -> Mutability { + if pat.any(|p| self.pat_bound_mutability(p) == Mutability::Mut) { + Mutability::Mut + } else { + Mutability::Not + } + } + + /// Checks if the pat contains a `ref mut` binding. Such paths makes the context of bounded expressions + /// mutable. For example in `let (ref mut x0, ref x1) = *x;` we need to use `DerefMut` for `*x` but in + /// `let (ref x0, ref x1) = *x;` we should use `Deref`. + fn pat_bound_mutability(&self, pat: PatId) -> Mutability { + let mut r = Mutability::Not; + self.body.walk_bindings_in_pat(pat, |b| { + if self.body.bindings[b].mode == BindingAnnotation::RefMut { + r = Mutability::Mut; + } + }); + r + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs index 5f839fc307aa..2480f8babac4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs @@ -5,7 +5,7 @@ use chalk_ir::Mutability; use hir_def::{ body::Body, - expr::{Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId}, + hir::{Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId}, path::Path, }; use hir_expand::name::Name; @@ -255,15 +255,15 @@ fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) } Pat::Wild => expected.clone(), - Pat::Range { start, end } => { - let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone())); - self.infer_expr(*end, &Expectation::has_type(start_ty)) + Pat::Range { .. } => { + // FIXME: do some checks here. + expected.clone() } &Pat::Lit(expr) => { // Don't emit type mismatches again, the expression lowering already did that. let ty = self.infer_lit_pat(expr, &expected); self.write_pat_ty(pat, ty.clone()); - return ty; + return self.pat_ty_after_adjustment(pat); } Pat::Box { inner } => match self.resolve_boxed_box() { Some(box_adt) => { @@ -298,22 +298,38 @@ fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) .type_mismatches .insert(pat.into(), TypeMismatch { expected, actual: ty.clone() }); } - self.write_pat_ty(pat, ty.clone()); - ty + self.write_pat_ty(pat, ty); + self.pat_ty_after_adjustment(pat) + } + + fn pat_ty_after_adjustment(&self, pat: PatId) -> Ty { + self.result + .pat_adjustments + .get(&pat) + .and_then(|x| x.first()) + .unwrap_or(&self.result.type_of_pat[pat]) + .clone() } fn infer_ref_pat( &mut self, - pat: PatId, + inner_pat: PatId, mutability: Mutability, expected: &Ty, default_bm: BindingMode, ) -> Ty { let expectation = match expected.as_reference() { Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), - _ => self.result.standard_types.unknown.clone(), + None => { + let inner_ty = self.table.new_type_var(); + let ref_ty = + TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner); + // Unification failure will be reported by the caller. + self.unify(&ref_ty, expected); + inner_ty + } }; - let subty = self.infer_pat(pat, &expectation, default_bm); + let subty = self.infer_pat(inner_pat, &expectation, default_bm); TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) } @@ -331,7 +347,7 @@ fn infer_bind_pat( } else { BindingMode::convert(mode) }; - self.result.pat_binding_modes.insert(pat, mode); + self.result.binding_modes.insert(binding, mode); let inner_ty = match subpat { Some(subpat) => self.infer_pat(subpat, &expected, default_bm), @@ -345,7 +361,7 @@ fn infer_bind_pat( } BindingMode::Move => inner_ty.clone(), }; - self.write_pat_ty(pat, bound_ty.clone()); + self.write_pat_ty(pat, inner_ty.clone()); self.write_binding_ty(binding, bound_ty); return inner_ty; } @@ -370,7 +386,7 @@ fn infer_slice_pat( if let &Some(slice_pat_id) = slice { let rest_pat_ty = match expected.kind(Interner) { TyKind::Array(_, length) => { - let len = try_const_usize(length); + let len = try_const_usize(self.db, length); let len = len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128)); TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate())) @@ -419,17 +435,10 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { // FIXME: ConstBlock/Path/Lit might actually evaluate to ref, but inference is unimplemented. Pat::Path(..) => true, Pat::ConstBlock(..) => true, - Pat::Lit(expr) => { - !matches!(body[*expr], Expr::Literal(Literal::String(..) | Literal::ByteString(..))) - } - Pat::Bind { id, subpat: Some(subpat), .. } - if matches!( - body.bindings[*id].mode, - BindingAnnotation::Mutable | BindingAnnotation::Unannotated - ) => - { - is_non_ref_pat(body, *subpat) - } + Pat::Lit(expr) => !matches!( + body[*expr], + Expr::Literal(Literal::String(..) | Literal::CString(..) | Literal::ByteString(..)) + ), Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false, } } @@ -437,7 +446,7 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { let mut res = false; body.walk_pats(pat_id, &mut |pat| { - res |= matches!(pat, Pat::Bind { id, .. } if body.bindings[*id].mode == BindingAnnotation::Ref); + res |= matches!(body[pat], Pat::Bind { id, .. } if body.bindings[id].mode == BindingAnnotation::Ref); }); res } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 2267fedaa8e9..95a20f983f1b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -4,7 +4,7 @@ use hir_def::{ path::{Path, PathSegment}, resolver::{ResolveValueResult, TypeNs, ValueNs}, - AdtId, AssocItemId, EnumVariantId, ItemContainerId, Lookup, + AdtId, AssocItemId, EnumVariantId, GenericDefId, ItemContainerId, Lookup, }; use hir_expand::name::Name; use stdx::never; @@ -13,6 +13,7 @@ builder::ParamKind, consteval, method_resolution::{self, VisibleFromModule}, + to_chalk_trait_id, utils::generics, InferenceDiagnostic, Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, ValueTyDefId, @@ -20,26 +21,43 @@ use super::{ExprOrPatId, InferenceContext, TraitRef}; -impl<'a> InferenceContext<'a> { +impl InferenceContext<'_> { pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { - let ty = self.resolve_value_path(path, id)?; - let ty = self.insert_type_vars(ty); + let (value_def, generic_def, substs) = match self.resolve_value_path(path, id)? { + ValuePathResolution::GenericDef(value_def, generic_def, substs) => { + (value_def, generic_def, substs) + } + ValuePathResolution::NonGeneric(ty) => return Some(ty), + }; + let substs = self.insert_type_vars(substs); + let substs = self.normalize_associated_types_in(substs); + + self.add_required_obligations_for_value_path(generic_def, &substs); + + let ty = self.db.value_ty(value_def).substitute(Interner, &substs); let ty = self.normalize_associated_types_in(ty); Some(ty) } - fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { + fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { - let Some(last) = path.segments().last() else { return None }; - let ty = self.make_ty(type_ref); - let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); + let last = path.segments().last()?; + + // Don't use `self.make_ty()` here as we need `orig_ns`. let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); - let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty); + let (ty, orig_ns) = ctx.lower_ty_ext(type_ref); + let ty = self.table.insert_type_vars(ty); + let ty = self.table.normalize_associated_types_in(ty); + + let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); + let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty); + let ty = self.table.insert_type_vars(ty); + let ty = self.table.normalize_associated_types_in(ty); self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? } else { // FIXME: report error, unresolved first path segment let value_or_partial = - self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?; + self.resolver.resolve_path_in_value_ns(self.db.upcast(), path)?; match value_or_partial { ResolveValueResult::ValueNs(it) => (it, None), @@ -49,9 +67,9 @@ fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { } }; - let typable: ValueTyDefId = match value { + let value_def = match value { ValueNs::LocalBinding(pat) => match self.result.type_of_binding.get(pat) { - Some(ty) => return Some(ty.clone()), + Some(ty) => return Some(ValuePathResolution::NonGeneric(ty.clone())), None => { never!("uninferred pattern?"); return None; @@ -75,28 +93,45 @@ fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { let substs = generics.placeholder_subst(self.db); let ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs); if let Some((AdtId::StructId(struct_id), substs)) = ty.as_adt() { - let ty = self.db.value_ty(struct_id.into()).substitute(Interner, &substs); - return Some(ty); + return Some(ValuePathResolution::GenericDef( + struct_id.into(), + struct_id.into(), + substs.clone(), + )); } else { // FIXME: report error, invalid Self reference return None; } } - ValueNs::GenericParam(it) => return Some(self.db.const_param_ty(it)), + ValueNs::GenericParam(it) => { + return Some(ValuePathResolution::NonGeneric(self.db.const_param_ty(it))) + } }; let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); - let substs = ctx.substs_from_path(path, typable, true); + let substs = ctx.substs_from_path(path, value_def, true); 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 generics = generics(self.db.upcast(), value_def.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) + + let Some(generic_def) = value_def.to_generic_def_id() else { + // `value_def` is the kind of item that can never be generic (i.e. statics, at least + // currently). We can just skip the binders to get its type. + let (ty, binders) = self.db.value_ty(value_def).into_value_and_skipped_binders(); + stdx::always!( + parent_substs.is_none() && binders.is_empty(Interner), + "non-empty binders for non-generic def", + ); + return Some(ValuePathResolution::NonGeneric(ty)); + }; + let builder = TyBuilder::subst_for_def(self.db, generic_def, parent_substs); + let substs = builder .fill(|x| { it.next().unwrap_or_else(|| match x { ParamKind::Type => self.result.standard_types.unknown.clone().cast(Interner), @@ -104,7 +139,35 @@ fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { }) }) .build(); - Some(ty) + + Some(ValuePathResolution::GenericDef(value_def, generic_def, substs)) + } + + fn add_required_obligations_for_value_path(&mut self, def: GenericDefId, subst: &Substitution) { + let predicates = self.db.generic_predicates(def); + for predicate in predicates.iter() { + let (predicate, binders) = + predicate.clone().substitute(Interner, &subst).into_value_and_skipped_binders(); + // Quantified where clauses are not yet handled. + stdx::always!(binders.is_empty(Interner)); + self.push_obligation(predicate.cast(Interner)); + } + + // We need to add `Self: Trait` obligation when `def` is a trait assoc item. + let container = match def { + GenericDefId::FunctionId(id) => id.lookup(self.db.upcast()).container, + GenericDefId::ConstId(id) => id.lookup(self.db.upcast()).container, + _ => return, + }; + + if let ItemContainerId::TraitId(trait_) = container { + let param_len = generics(self.db.upcast(), def).len_self(); + let parent_subst = + Substitution::from_iter(Interner, subst.iter(Interner).skip(param_len)); + let trait_ref = + TraitRef { trait_id: to_chalk_trait_id(trait_), substitution: parent_subst }; + self.push_obligation(trait_ref.cast(Interner)); + } } fn resolve_assoc_item( @@ -169,7 +232,7 @@ fn resolve_trait_assoc_item( ) -> Option<(ValueNs, Substitution)> { let trait_ = trait_ref.hir_trait_id(); let item = - self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| { + self.db.trait_data(trait_).items.iter().map(|(_name, id)| *id).find_map(|item| { match item { AssocItemId::FunctionId(func) => { if segment.name == &self.db.function_data(func).name { @@ -288,7 +351,7 @@ fn resolve_enum_variant_on_ty( name: &Name, id: ExprOrPatId, ) -> Option<(ValueNs, Substitution)> { - let ty = self.resolve_ty_shallow(ty); + let ty = self.resolve_ty_shallow(&ty); let (enum_id, subst) = match ty.as_adt() { Some((AdtId::EnumId(e), subst)) => (e, subst), _ => return None, @@ -300,3 +363,10 @@ fn resolve_enum_variant_on_ty( Some((ValueNs::EnumVariantId(variant), subst.clone())) } } + +enum ValuePathResolution { + // It's awkward to wrap a single ID in two enums, but we need both and this saves fallible + // conversion between them + `unwrap()`. + GenericDef(ValueTyDefId, GenericDefId, Substitution), + NonGeneric(Ty), +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index 504f0743aa94..e33d8f1795e2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -1,23 +1,25 @@ //! Unification and canonicalization logic. -use std::{fmt, iter, mem, sync::Arc}; +use std::{fmt, iter, mem}; use chalk_ir::{ cast::Cast, fold::TypeFoldable, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy, IntTy, TyVariableKind, UniverseIndex, }; use chalk_solve::infer::ParameterEnaVariableExt; +use either::Either; use ena::unify::UnifyKey; -use hir_def::{FunctionId, TraitId}; use hir_expand::name; use stdx::never; +use triomphe::Arc; use super::{InferOk, InferResult, InferenceContext, TypeError}; use crate::{ - db::HirDatabase, fold_tys, static_lifetime, traits::FnTrait, AliasEq, AliasTy, BoundVar, - Canonical, Const, DebruijnIndex, GenericArg, GenericArgData, Goal, Guidance, InEnvironment, - InferenceVar, Interner, Lifetime, ParamKind, ProjectionTy, ProjectionTyExt, Scalar, Solution, - Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind, + consteval::unknown_const, db::HirDatabase, fold_tys_and_consts, static_lifetime, + to_chalk_trait_id, traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, Const, ConstValue, + DebruijnIndex, GenericArg, GenericArgData, Goal, Guidance, InEnvironment, InferenceVar, + Interner, Lifetime, ParamKind, ProjectionTy, ProjectionTyExt, Scalar, Solution, Substitution, + TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind, }; impl<'a> InferenceContext<'a> { @@ -130,7 +132,7 @@ pub(crate) fn unify( } bitflags::bitflags! { - #[derive(Default)] + #[derive(Default, Clone, Copy)] pub(crate) struct TypeVariableFlags: u8 { const DIVERGING = 1 << 0; const INTEGER = 1 << 1; @@ -230,14 +232,40 @@ pub(crate) fn canonicalize + HasInterner Ty { - fold_tys( + pub(crate) fn normalize_associated_types_in(&mut self, ty: T) -> T + where + T: HasInterner + TypeFoldable, + { + fold_tys_and_consts( ty, - |ty, _| match ty.kind(Interner) { - TyKind::Alias(AliasTy::Projection(proj_ty)) => { - self.normalize_projection_ty(proj_ty.clone()) - } - _ => ty, + |e, _| match e { + Either::Left(ty) => Either::Left(match ty.kind(Interner) { + TyKind::Alias(AliasTy::Projection(proj_ty)) => { + self.normalize_projection_ty(proj_ty.clone()) + } + _ => ty, + }), + Either::Right(c) => Either::Right(match &c.data(Interner).value { + chalk_ir::ConstValue::Concrete(cc) => match &cc.interned { + crate::ConstScalar::UnevaluatedConst(c_id, subst) => { + // FIXME: Ideally here we should do everything that we do with type alias, i.e. adding a variable + // and registering an obligation. But it needs chalk support, so we handle the most basic + // case (a non associated const without generic parameters) manually. + if subst.len(Interner) == 0 { + if let Ok(eval) = self.db.const_eval((*c_id).into(), subst.clone()) + { + eval + } else { + unknown_const(c.data(Interner).ty.clone()) + } + } else { + unknown_const(c.data(Interner).ty.clone()) + } + } + _ => c, + }, + _ => c, + }), }, DebruijnIndex::INNERMOST, ) @@ -463,7 +491,8 @@ pub(crate) fn run_in_snapshot(&mut self, f: impl FnOnce(&mut InferenceTable<' pub(crate) fn try_obligation(&mut self, goal: Goal) -> Option { let in_env = InEnvironment::new(&self.trait_env.env, goal); let canonicalized = self.canonicalize(in_env); - let solution = self.db.trait_solve(self.trait_env.krate, canonicalized.value); + let solution = + self.db.trait_solve(self.trait_env.krate, self.trait_env.block, canonicalized.value); solution } @@ -598,7 +627,11 @@ fn try_resolve_obligation( &mut self, canonicalized: &Canonicalized>, ) -> bool { - let solution = self.db.trait_solve(self.trait_env.krate, canonicalized.value.clone()); + let solution = self.db.trait_solve( + self.trait_env.krate, + self.trait_env.block, + canonicalized.value.clone(), + ); match solution { Some(Solution::Unique(canonical_subst)) => { @@ -631,10 +664,13 @@ pub(crate) fn callable_sig( &mut self, ty: &Ty, num_args: usize, - ) -> Option<(Option<(TraitId, FunctionId)>, Vec, Ty)> { + ) -> Option<(Option, Vec, Ty)> { match ty.callable_sig(self.db) { Some(sig) => Some((None, sig.params().to_vec(), sig.ret().clone())), - None => self.callable_sig_from_fn_trait(ty, num_args), + None => { + let (f, args_ty, return_ty) = self.callable_sig_from_fn_trait(ty, num_args)?; + Some((Some(f), args_ty, return_ty)) + } } } @@ -642,7 +678,7 @@ fn callable_sig_from_fn_trait( &mut self, ty: &Ty, num_args: usize, - ) -> Option<(Option<(TraitId, FunctionId)>, Vec, Ty)> { + ) -> Option<(FnTrait, Vec, Ty)> { let krate = self.trait_env.krate; let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?; let trait_data = self.db.trait_data(fn_once_trait); @@ -676,23 +712,90 @@ fn callable_sig_from_fn_trait( }; let trait_env = self.trait_env.env.clone(); + let mut trait_ref = projection.trait_ref(self.db); let obligation = InEnvironment { - goal: projection.trait_ref(self.db).cast(Interner), - environment: trait_env, + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), }; let canonical = self.canonicalize(obligation.clone()); - if self.db.trait_solve(krate, canonical.value.cast(Interner)).is_some() { + if self + .db + .trait_solve(krate, self.trait_env.block, canonical.value.cast(Interner)) + .is_some() + { self.register_obligation(obligation.goal); let return_ty = self.normalize_projection_ty(projection); - Some(( - Some(fn_once_trait).zip(trait_data.method_by_name(&name!(call_once))), - arg_tys, - return_ty, - )) + for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] { + let fn_x_trait = fn_x.get_id(self.db, krate)?; + trait_ref.trait_id = to_chalk_trait_id(fn_x_trait); + let obligation: chalk_ir::InEnvironment> = InEnvironment { + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), + }; + let canonical = self.canonicalize(obligation.clone()); + if self + .db + .trait_solve(krate, self.trait_env.block, canonical.value.cast(Interner)) + .is_some() + { + return Some((fn_x, arg_tys, return_ty)); + } + } + unreachable!("It should at least implement FnOnce at this point"); } else { None } } + + pub(super) fn insert_type_vars(&mut self, ty: T) -> T + where + T: HasInterner + TypeFoldable, + { + fold_tys_and_consts( + ty, + |x, _| match x { + Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)), + Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)), + }, + DebruijnIndex::INNERMOST, + ) + } + + /// Replaces `Ty::Error` by a new type var, so we can maybe still infer it. + pub(super) fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { + match ty.kind(Interner) { + TyKind::Error => self.new_type_var(), + TyKind::InferenceVar(..) => { + let ty_resolved = self.resolve_ty_shallow(&ty); + if ty_resolved.is_unknown() { + self.new_type_var() + } else { + ty + } + } + _ => ty, + } + } + + /// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it. + pub(super) fn insert_const_vars_shallow(&mut self, c: Const) -> Const { + let data = c.data(Interner); + match &data.value { + ConstValue::Concrete(cc) => match &cc.interned { + crate::ConstScalar::Unknown => self.new_const_var(data.ty.clone()), + // try to evaluate unevaluated const. Replace with new var if const eval failed. + crate::ConstScalar::UnevaluatedConst(id, subst) => { + if let Ok(eval) = self.db.const_eval(*id, subst.clone()) { + eval + } else { + self.new_const_var(data.ty.clone()) + } + } + _ => c, + }, + _ => c, + } + } } impl<'a> fmt::Debug for InferenceTable<'a> { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs b/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs index 36af78153d43..e5038543b68e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs @@ -6,9 +6,10 @@ DebruijnIndex, }; use hir_def::{ - adt::VariantData, attr::Attrs, visibility::Visibility, AdtId, EnumVariantId, HasModule, Lookup, - ModuleId, VariantId, + attr::Attrs, data::adt::VariantData, visibility::Visibility, AdtId, EnumVariantId, HasModule, + Lookup, ModuleId, VariantId, }; +use rustc_hash::FxHashSet; use crate::{ consteval::try_const_usize, db::HirDatabase, Binders, Interner, Substitution, Ty, TyKind, @@ -16,7 +17,8 @@ /// Checks whether a type is visibly uninhabited from a particular module. pub(crate) fn is_ty_uninhabited_from(ty: &Ty, target_mod: ModuleId, db: &dyn HirDatabase) -> bool { - let mut uninhabited_from = UninhabitedFrom { target_mod, db }; + let mut uninhabited_from = + UninhabitedFrom { target_mod, db, max_depth: 500, recursive_ty: FxHashSet::default() }; let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST); inhabitedness == BREAK_VISIBLY_UNINHABITED } @@ -32,7 +34,8 @@ pub(crate) fn is_enum_variant_uninhabited_from( let vars_attrs = db.variants_attrs(variant.parent); let is_local = variant.parent.lookup(db.upcast()).container.krate() == target_mod.krate(); - let mut uninhabited_from = UninhabitedFrom { target_mod, db }; + let mut uninhabited_from = + UninhabitedFrom { target_mod, db, max_depth: 500, recursive_ty: FxHashSet::default() }; let inhabitedness = uninhabited_from.visit_variant( variant.into(), &enum_data.variants[variant.local_id].variant_data, @@ -45,6 +48,9 @@ pub(crate) fn is_enum_variant_uninhabited_from( struct UninhabitedFrom<'a> { target_mod: ModuleId, + recursive_ty: FxHashSet, + // guard for preventing stack overflow in non trivial non terminating types + max_depth: usize, db: &'a dyn HirDatabase, } @@ -65,17 +71,27 @@ fn visit_ty( ty: &Ty, outer_binder: DebruijnIndex, ) -> ControlFlow { - match ty.kind(Interner) { + if self.recursive_ty.contains(ty) || self.max_depth == 0 { + // rustc considers recursive types always inhabited. I think it is valid to consider + // recursive types as always uninhabited, but we should do what rustc is doing. + return CONTINUE_OPAQUELY_INHABITED; + } + self.recursive_ty.insert(ty.clone()); + self.max_depth -= 1; + let r = match ty.kind(Interner) { TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst), TyKind::Never => BREAK_VISIBLY_UNINHABITED, TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder), - TyKind::Array(item_ty, len) => match try_const_usize(len) { + TyKind::Array(item_ty, len) => match try_const_usize(self.db, len) { Some(0) | None => CONTINUE_OPAQUELY_INHABITED, Some(1..) => item_ty.super_visit_with(self, outer_binder), }, TyKind::Ref(..) | _ => CONTINUE_OPAQUELY_INHABITED, - } + }; + self.recursive_ty.remove(ty); + self.max_depth += 1; + r } fn interner(&self) -> Interner { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs index aea7e9762fdf..89f7d9c4f4ab 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs @@ -7,7 +7,8 @@ use hir_def::TypeAliasId; use intern::{impl_internable, Interned}; use smallvec::SmallVec; -use std::{fmt, sync::Arc}; +use std::fmt; +use triomphe::Arc; #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Interner; @@ -43,7 +44,7 @@ fn deref(&self) -> &Self::Target { ); impl chalk_ir::interner::Interner for Interner { - type InternedType = Interned>>; + type InternedType = Interned>>; type InternedLifetime = Interned>>; type InternedConst = Interned>>; type InternedConcreteConst = ConstScalar; @@ -51,8 +52,8 @@ impl chalk_ir::interner::Interner for Interner { type InternedGoal = Arc>; type InternedGoals = Vec>; type InternedSubstitution = Interned>>; - type InternedProgramClause = chalk_ir::ProgramClauseData; type InternedProgramClauses = Interned>>>; + type InternedProgramClause = chalk_ir::ProgramClauseData; type InternedQuantifiedWhereClauses = Interned>>>; type InternedVariableKinds = Interned>>>; @@ -86,6 +87,27 @@ fn debug_assoc_type_id( tls::with_current_program(|prog| Some(prog?.debug_assoc_type_id(id, fmt))) } + fn debug_opaque_ty_id( + opaque_ty_id: chalk_ir::OpaqueTyId, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + Some(write!(fmt, "OpaqueTy#{}", opaque_ty_id.0)) + } + + fn debug_fn_def_id( + fn_def_id: chalk_ir::FnDefId, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + tls::with_current_program(|prog| Some(prog?.debug_fn_def_id(fn_def_id, fmt))) + } + + fn debug_closure_id( + _fn_def_id: chalk_ir::ClosureId, + _fmt: &mut fmt::Formatter<'_>, + ) -> Option { + None + } + fn debug_alias( alias: &chalk_ir::AliasTy, fmt: &mut fmt::Formatter<'_>, @@ -113,13 +135,6 @@ fn debug_opaque_ty( Some(write!(fmt, "{:?}", opaque_ty.opaque_ty_id)) } - fn debug_opaque_ty_id( - opaque_ty_id: chalk_ir::OpaqueTyId, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "OpaqueTy#{}", opaque_ty_id.0)) - } - fn debug_ty(ty: &chalk_ir::Ty, fmt: &mut fmt::Formatter<'_>) -> Option { Some(write!(fmt, "{:?}", ty.data(Interner))) } @@ -131,6 +146,13 @@ fn debug_lifetime( Some(write!(fmt, "{:?}", lifetime.data(Interner))) } + fn debug_const( + constant: &chalk_ir::Const, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + Some(write!(fmt, "{:?}", constant.data(Interner))) + } + fn debug_generic_arg( parameter: &GenericArg, fmt: &mut fmt::Formatter<'_>, @@ -138,69 +160,42 @@ fn debug_generic_arg( Some(write!(fmt, "{:?}", parameter.data(Interner).inner_debug())) } - fn debug_goal(goal: &Goal, fmt: &mut fmt::Formatter<'_>) -> Option { - let goal_data = goal.data(Interner); - Some(write!(fmt, "{goal_data:?}")) - } - - fn debug_goals( - goals: &chalk_ir::Goals, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", goals.debug(Interner))) - } - - fn debug_program_clause_implication( - pci: &chalk_ir::ProgramClauseImplication, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", pci.debug(Interner))) - } - - fn debug_substitution( - substitution: &chalk_ir::Substitution, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", substitution.debug(Interner))) - } - - fn debug_separator_trait_ref( - separator_trait_ref: &chalk_ir::SeparatorTraitRef<'_, Interner>, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", separator_trait_ref.debug(Interner))) - } - - fn debug_fn_def_id( - fn_def_id: chalk_ir::FnDefId, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - tls::with_current_program(|prog| Some(prog?.debug_fn_def_id(fn_def_id, fmt))) - } - fn debug_const( - constant: &chalk_ir::Const, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", constant.data(Interner))) - } fn debug_variable_kinds( variable_kinds: &chalk_ir::VariableKinds, fmt: &mut fmt::Formatter<'_>, ) -> Option { Some(write!(fmt, "{:?}", variable_kinds.as_slice(Interner))) } + fn debug_variable_kinds_with_angles( variable_kinds: &chalk_ir::VariableKinds, fmt: &mut fmt::Formatter<'_>, ) -> Option { Some(write!(fmt, "{:?}", variable_kinds.inner_debug(Interner))) } + fn debug_canonical_var_kinds( canonical_var_kinds: &chalk_ir::CanonicalVarKinds, fmt: &mut fmt::Formatter<'_>, ) -> Option { Some(write!(fmt, "{:?}", canonical_var_kinds.as_slice(Interner))) } + fn debug_goal(goal: &Goal, fmt: &mut fmt::Formatter<'_>) -> Option { + let goal_data = goal.data(Interner); + Some(write!(fmt, "{goal_data:?}")) + } + fn debug_goals( + goals: &chalk_ir::Goals, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + Some(write!(fmt, "{:?}", goals.debug(Interner))) + } + fn debug_program_clause_implication( + pci: &chalk_ir::ProgramClauseImplication, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + Some(write!(fmt, "{:?}", pci.debug(Interner))) + } fn debug_program_clause( clause: &chalk_ir::ProgramClause, fmt: &mut fmt::Formatter<'_>, @@ -213,6 +208,19 @@ fn debug_program_clauses( ) -> Option { Some(write!(fmt, "{:?}", clauses.as_slice(Interner))) } + fn debug_substitution( + substitution: &chalk_ir::Substitution, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + Some(write!(fmt, "{:?}", substitution.debug(Interner))) + } + fn debug_separator_trait_ref( + separator_trait_ref: &chalk_ir::SeparatorTraitRef<'_, Interner>, + fmt: &mut fmt::Formatter<'_>, + ) -> Option { + Some(write!(fmt, "{:?}", separator_trait_ref.debug(Interner))) + } + fn debug_quantified_where_clauses( clauses: &chalk_ir::QuantifiedWhereClauses, fmt: &mut fmt::Formatter<'_>, @@ -220,6 +228,13 @@ fn debug_quantified_where_clauses( Some(write!(fmt, "{:?}", clauses.as_slice(Interner))) } + fn debug_constraints( + _clauses: &chalk_ir::Constraints, + _fmt: &mut fmt::Formatter<'_>, + ) -> Option { + None + } + fn intern_ty(self, kind: chalk_ir::TyKind) -> Self::InternedType { let flags = kind.compute_flags(self); Interned::new(InternedWrapper(chalk_ir::TyData { kind, flags })) @@ -272,6 +287,10 @@ fn intern_goal(self, goal: GoalData) -> Self::InternedGoal { Arc::new(goal) } + fn goal_data(self, goal: &Self::InternedGoal) -> &GoalData { + goal + } + fn intern_goals( self, data: impl IntoIterator, E>>, @@ -279,10 +298,6 @@ fn intern_goals( data.into_iter().collect() } - fn goal_data(self, goal: &Self::InternedGoal) -> &GoalData { - goal - } - fn goals_data(self, goals: &Self::InternedGoals) -> &[Goal] { goals } @@ -367,32 +382,18 @@ fn canonical_var_kinds_data( ) -> &[chalk_ir::CanonicalVarKind] { canonical_var_kinds } - fn intern_constraints( self, data: impl IntoIterator>, E>>, ) -> Result { data.into_iter().collect() } - fn constraints_data( self, constraints: &Self::InternedConstraints, ) -> &[chalk_ir::InEnvironment>] { constraints } - fn debug_closure_id( - _fn_def_id: chalk_ir::ClosureId, - _fmt: &mut fmt::Formatter<'_>, - ) -> Option { - None - } - fn debug_constraints( - _clauses: &chalk_ir::Constraints, - _fmt: &mut fmt::Formatter<'_>, - ) -> Option { - None - } fn intern_variances( self, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs index 5308c72161b2..85ed46b96321 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs @@ -1,19 +1,65 @@ //! Functions to detect special lang items -use hir_def::{lang_item::LangItem, AdtId, HasModule}; +use hir_def::{data::adt::StructFlags, lang_item::LangItem, AdtId}; +use hir_expand::name::Name; use crate::db::HirDatabase; -pub fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool { - let krate = adt.module(db.upcast()).krate(); - let box_adt = - db.lang_item(krate, LangItem::OwnedBox).and_then(|it| it.as_struct()).map(AdtId::from); - Some(adt) == box_adt +pub fn is_box(db: &dyn HirDatabase, adt: AdtId) -> bool { + let AdtId::StructId(id) = adt else { return false }; + db.struct_data(id).flags.contains(StructFlags::IS_BOX) } -pub fn is_unsafe_cell(adt: AdtId, db: &dyn HirDatabase) -> bool { - let krate = adt.module(db.upcast()).krate(); - let box_adt = - db.lang_item(krate, LangItem::UnsafeCell).and_then(|it| it.as_struct()).map(AdtId::from); - Some(adt) == box_adt +pub fn is_unsafe_cell(db: &dyn HirDatabase, adt: AdtId) -> bool { + let AdtId::StructId(id) = adt else { return false }; + db.struct_data(id).flags.contains(StructFlags::IS_UNSAFE_CELL) +} + +pub fn lang_items_for_bin_op(op: syntax::ast::BinaryOp) -> Option<(Name, LangItem)> { + use hir_expand::name; + use syntax::ast::{ArithOp, BinaryOp, CmpOp, Ordering}; + Some(match op { + BinaryOp::LogicOp(_) => return None, + BinaryOp::ArithOp(aop) => match aop { + ArithOp::Add => (name![add], LangItem::Add), + ArithOp::Mul => (name![mul], LangItem::Mul), + ArithOp::Sub => (name![sub], LangItem::Sub), + ArithOp::Div => (name![div], LangItem::Div), + ArithOp::Rem => (name![rem], LangItem::Rem), + ArithOp::Shl => (name![shl], LangItem::Shl), + ArithOp::Shr => (name![shr], LangItem::Shr), + ArithOp::BitXor => (name![bitxor], LangItem::BitXor), + ArithOp::BitOr => (name![bitor], LangItem::BitOr), + ArithOp::BitAnd => (name![bitand], LangItem::BitAnd), + }, + BinaryOp::Assignment { op: Some(aop) } => match aop { + ArithOp::Add => (name![add_assign], LangItem::AddAssign), + ArithOp::Mul => (name![mul_assign], LangItem::MulAssign), + ArithOp::Sub => (name![sub_assign], LangItem::SubAssign), + ArithOp::Div => (name![div_assign], LangItem::DivAssign), + ArithOp::Rem => (name![rem_assign], LangItem::RemAssign), + ArithOp::Shl => (name![shl_assign], LangItem::ShlAssign), + ArithOp::Shr => (name![shr_assign], LangItem::ShrAssign), + ArithOp::BitXor => (name![bitxor_assign], LangItem::BitXorAssign), + ArithOp::BitOr => (name![bitor_assign], LangItem::BitOrAssign), + ArithOp::BitAnd => (name![bitand_assign], LangItem::BitAndAssign), + }, + BinaryOp::CmpOp(cop) => match cop { + CmpOp::Eq { negated: false } => (name![eq], LangItem::PartialEq), + CmpOp::Eq { negated: true } => (name![ne], LangItem::PartialEq), + CmpOp::Ord { ordering: Ordering::Less, strict: false } => { + (name![le], LangItem::PartialOrd) + } + CmpOp::Ord { ordering: Ordering::Less, strict: true } => { + (name![lt], LangItem::PartialOrd) + } + CmpOp::Ord { ordering: Ordering::Greater, strict: false } => { + (name![ge], LangItem::PartialOrd) + } + CmpOp::Ord { ordering: Ordering::Greater, strict: true } => { + (name![gt], LangItem::PartialOrd) + } + }, + BinaryOp::Assignment { op: None } => return None, + }) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index b95bb01fcefe..35d3407c16c1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -1,19 +1,23 @@ //! Compute the binary representation of a type use base_db::CrateId; -use chalk_ir::{AdtId, TyKind}; +use chalk_ir::{AdtId, FloatTy, IntTy, TyKind, UintTy}; use hir_def::{ layout::{ - Abi, FieldsShape, Integer, Layout, LayoutCalculator, LayoutError, Primitive, ReprOptions, - RustcEnumVariantIdx, Scalar, Size, StructKind, TargetDataLayout, Variants, WrappingRange, + Abi, FieldsShape, Integer, LayoutCalculator, LayoutS, Primitive, ReprOptions, Scalar, Size, + StructKind, TargetDataLayout, WrappingRange, }, - LocalFieldId, + LocalEnumVariantId, LocalFieldId, }; +use la_arena::{Idx, RawIdx}; use stdx::never; +use triomphe::Arc; -use crate::{consteval::try_const_usize, db::HirDatabase, Interner, Substitution, Ty}; +use crate::{ + consteval::try_const_usize, db::HirDatabase, infer::normalize, layout::adt::struct_variant_idx, + utils::ClosureSubst, Interner, Substitution, TraitEnvironment, Ty, +}; -use self::adt::struct_variant_idx; pub use self::{ adt::{layout_of_adt_query, layout_of_adt_recover}, target::target_data_layout_query, @@ -28,6 +32,34 @@ macro_rules! user_error { mod adt; mod target; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct RustcEnumVariantIdx(pub LocalEnumVariantId); + +impl rustc_index::vec::Idx for RustcEnumVariantIdx { + fn new(idx: usize) -> Self { + RustcEnumVariantIdx(Idx::from_raw(RawIdx::from(idx as u32))) + } + + fn index(self) -> usize { + u32::from(self.0.into_raw()) as usize + } +} + +pub type Layout = LayoutS; +pub type TagEncoding = hir_def::layout::TagEncoding; +pub type Variants = hir_def::layout::Variants; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum LayoutError { + UserError(String), + SizeOverflow, + TargetLayoutNotAvailable, + HasPlaceholder, + HasErrorType, + NotImplemented, + Unknown, +} + struct LayoutCx<'a> { krate: CrateId, target: &'a TargetDataLayout, @@ -45,20 +77,18 @@ fn current_data_layout(&self) -> &'a TargetDataLayout { } } -fn scalar_unit(dl: &TargetDataLayout, value: Primitive) -> Scalar { - Scalar::Initialized { value, valid_range: WrappingRange::full(value.size(dl)) } -} - -fn scalar(dl: &TargetDataLayout, value: Primitive) -> Layout { - Layout::scalar(dl, scalar_unit(dl, value)) -} - -pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result { +pub fn layout_of_ty_query( + db: &dyn HirDatabase, + ty: Ty, + krate: CrateId, +) -> Result, LayoutError> { let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) }; let cx = LayoutCx { krate, target: &target }; let dl = &*cx.current_data_layout(); - Ok(match ty.kind(Interner) { - TyKind::Adt(AdtId(def), subst) => db.layout_of_adt(*def, subst.clone())?, + let trait_env = Arc::new(TraitEnvironment::empty(krate)); + let ty = normalize(db, trait_env, ty.clone()); + let result = match ty.kind(Interner) { + TyKind::Adt(AdtId(def), subst) => return db.layout_of_adt(*def, subst.clone(), krate), TyKind::Scalar(s) => match s { chalk_ir::Scalar::Bool => Layout::scalar( dl, @@ -78,12 +108,12 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result dl.ptr_sized_integer(), - chalk_ir::IntTy::I8 => Integer::I8, - chalk_ir::IntTy::I16 => Integer::I16, - chalk_ir::IntTy::I32 => Integer::I32, - chalk_ir::IntTy::I64 => Integer::I64, - chalk_ir::IntTy::I128 => Integer::I128, + IntTy::Isize => dl.ptr_sized_integer(), + IntTy::I8 => Integer::I8, + IntTy::I16 => Integer::I16, + IntTy::I32 => Integer::I32, + IntTy::I64 => Integer::I64, + IntTy::I128 => Integer::I128, }, true, ), @@ -92,12 +122,12 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result dl.ptr_sized_integer(), - chalk_ir::UintTy::U8 => Integer::I8, - chalk_ir::UintTy::U16 => Integer::I16, - chalk_ir::UintTy::U32 => Integer::I32, - chalk_ir::UintTy::U64 => Integer::I64, - chalk_ir::UintTy::U128 => Integer::I128, + UintTy::Usize => dl.ptr_sized_integer(), + UintTy::U8 => Integer::I8, + UintTy::U16 => Integer::I16, + UintTy::U32 => Integer::I32, + UintTy::U64 => Integer::I64, + UintTy::U128 => Integer::I128, }, false, ), @@ -105,8 +135,8 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result scalar( dl, match f { - chalk_ir::FloatTy::F32 => Primitive::F32, - chalk_ir::FloatTy::F64 => Primitive::F64, + FloatTy::F32 => Primitive::F32, + FloatTy::F64 => Primitive::F64, }, ), }, @@ -115,17 +145,17 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result, _>>()?; - let fields = fields.iter().collect::>(); + let fields = fields.iter().map(|x| &**x).collect::>(); let fields = fields.iter().collect::>(); cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)? } TyKind::Array(element, count) => { - let count = try_const_usize(&count).ok_or(LayoutError::UserError( - "mismatched type of const generic parameter".to_string(), + let count = try_const_usize(db, &count).ok_or(LayoutError::UserError( + "unevaluated or mistyped const generic parameter".to_string(), ))? as u64; - let element = layout_of_ty(db, element, krate)?; + let element = db.layout_of_ty(element.clone(), krate)?; let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; let abi = if count != 0 && matches!(element.abi, Abi::Uninhabited) { @@ -146,7 +176,7 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result { - let element = layout_of_ty(db, element, krate)?; + let element = db.layout_of_ty(element.clone(), krate)?; Layout { variants: Variants::Single { index: struct_variant_idx() }, fields: FieldsShape::Array { stride: element.size, count: 0 }, @@ -180,7 +210,7 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result { // pointee is sized - return Ok(Layout::scalar(dl, data_ptr)); + return Ok(Arc::new(Layout::scalar(dl, data_ptr))); } }; @@ -222,23 +252,51 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result { let infer = db.infer(func.into()); - layout_of_ty(db, &infer.type_of_rpit[idx], krate)? + return db.layout_of_ty(infer.type_of_rpit[idx].clone(), krate); } crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { return Err(LayoutError::NotImplemented) } } } - TyKind::Closure(_, _) | TyKind::Generator(_, _) | TyKind::GeneratorWitness(_, _) => { + TyKind::Closure(c, subst) => { + let (def, _) = db.lookup_intern_closure((*c).into()); + let infer = db.infer(def); + let (captures, _) = infer.closure_info(c); + let fields = captures + .iter() + .map(|x| { + db.layout_of_ty( + x.ty.clone().substitute(Interner, ClosureSubst(subst).parent_subst()), + krate, + ) + }) + .collect::, _>>()?; + let fields = fields.iter().map(|x| &**x).collect::>(); + let fields = fields.iter().collect::>(); + cx.univariant(dl, &fields, &ReprOptions::default(), StructKind::AlwaysSized) + .ok_or(LayoutError::Unknown)? + } + TyKind::Generator(_, _) | TyKind::GeneratorWitness(_, _) => { return Err(LayoutError::NotImplemented) } + TyKind::Error => return Err(LayoutError::HasErrorType), TyKind::AssociatedType(_, _) - | TyKind::Error | TyKind::Alias(_) | TyKind::Placeholder(_) | TyKind::BoundVar(_) | TyKind::InferenceVar(_, _) => return Err(LayoutError::HasPlaceholder), - }) + }; + Ok(Arc::new(result)) +} + +pub fn layout_of_ty_recover( + _: &dyn HirDatabase, + _: &[String], + _: &Ty, + _: &CrateId, +) -> Result, LayoutError> { + user_error!("infinite sized recursive type"); } fn layout_of_unit(cx: &LayoutCx<'_>, dl: &TargetDataLayout) -> Result { @@ -274,5 +332,13 @@ fn field_ty( db.field_types(def)[fd].clone().substitute(Interner, subst) } +fn scalar_unit(dl: &TargetDataLayout, value: Primitive) -> Scalar { + Scalar::Initialized { value, valid_range: WrappingRange::full(value.size(dl)) } +} + +fn scalar(dl: &TargetDataLayout, value: Primitive) -> Layout { + Layout::scalar(dl, scalar_unit(dl, value)) +} + #[cfg(test)] mod tests; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index b22d0fe8ded1..bd2752a7119a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -1,18 +1,25 @@ //! Compute the binary representation of structs, unions and enums -use std::ops::Bound; +use std::{cmp, ops::Bound}; +use base_db::CrateId; use hir_def::{ - adt::VariantData, - layout::{Integer, IntegerExt, Layout, LayoutCalculator, LayoutError, RustcEnumVariantIdx}, - AdtId, EnumVariantId, HasModule, LocalEnumVariantId, VariantId, + data::adt::VariantData, + layout::{Integer, LayoutCalculator, ReprOptions, TargetDataLayout}, + AdtId, EnumVariantId, LocalEnumVariantId, VariantId, }; use la_arena::RawIdx; use smallvec::SmallVec; +use triomphe::Arc; -use crate::{db::HirDatabase, lang_items::is_unsafe_cell, layout::field_ty, Substitution}; +use crate::{ + db::HirDatabase, + lang_items::is_unsafe_cell, + layout::{field_ty, Layout, LayoutError, RustcEnumVariantIdx}, + Substitution, +}; -use super::{layout_of_ty, LayoutCx}; +use super::LayoutCx; pub(crate) fn struct_variant_idx() -> RustcEnumVariantIdx { RustcEnumVariantIdx(LocalEnumVariantId::from_raw(RawIdx::from(0))) @@ -22,29 +29,29 @@ pub fn layout_of_adt_query( db: &dyn HirDatabase, def: AdtId, subst: Substitution, -) -> Result { - let krate = def.module(db.upcast()).krate(); + krate: CrateId, +) -> Result, LayoutError> { let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) }; let cx = LayoutCx { krate, target: &target }; let dl = cx.current_data_layout(); let handle_variant = |def: VariantId, var: &VariantData| { var.fields() .iter() - .map(|(fd, _)| layout_of_ty(db, &field_ty(db, def, fd, &subst), cx.krate)) + .map(|(fd, _)| db.layout_of_ty(field_ty(db, def, fd, &subst), cx.krate)) .collect::, _>>() }; - let (variants, is_enum, is_union, repr) = match def { + let (variants, repr) = match def { AdtId::StructId(s) => { let data = db.struct_data(s); let mut r = SmallVec::<[_; 1]>::new(); r.push(handle_variant(s.into(), &data.variant_data)?); - (r, false, false, data.repr.unwrap_or_default()) + (r, data.repr.unwrap_or_default()) } AdtId::UnionId(id) => { let data = db.union_data(id); let mut r = SmallVec::new(); r.push(handle_variant(id.into(), &data.variant_data)?); - (r, false, true, data.repr.unwrap_or_default()) + (r, data.repr.unwrap_or_default()) } AdtId::EnumId(e) => { let data = db.enum_data(e); @@ -58,22 +65,24 @@ pub fn layout_of_adt_query( ) }) .collect::, _>>()?; - (r, true, false, data.repr.unwrap_or_default()) + (r, data.repr.unwrap_or_default()) } }; - let variants = - variants.iter().map(|x| x.iter().collect::>()).collect::>(); + let variants = variants + .iter() + .map(|x| x.iter().map(|x| &**x).collect::>()) + .collect::>(); let variants = variants.iter().map(|x| x.iter().collect()).collect(); - if is_union { - cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown) + let result = if matches!(def, AdtId::UnionId(..)) { + cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown)? } else { cx.layout_of_struct_or_enum( &repr, &variants, - is_enum, - is_unsafe_cell(def, db), + matches!(def, AdtId::EnumId(..)), + is_unsafe_cell(db, def), layout_scalar_valid_range(db, def), - |min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)), + |min, max| repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)), variants.iter_enumerated().filter_map(|(id, _)| { let AdtId::EnumId(e) = def else { return None }; let d = @@ -90,15 +99,16 @@ pub fn layout_of_adt_query( // .iter_enumerated() // .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32())) repr.inhibit_enum_layout_opt(), - !is_enum + !matches!(def, AdtId::EnumId(..)) && variants .iter() .next() .and_then(|x| x.last().map(|x| x.is_unsized())) .unwrap_or(true), ) - .ok_or(LayoutError::SizeOverflow) - } + .ok_or(LayoutError::SizeOverflow)? + }; + Ok(Arc::new(result)) } fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound, Bound) { @@ -122,6 +132,54 @@ pub fn layout_of_adt_recover( _: &[String], _: &AdtId, _: &Substitution, -) -> Result { + _: &CrateId, +) -> Result, LayoutError> { user_error!("infinite sized recursive type"); } + +/// Finds the appropriate Integer type and signedness for the given +/// signed discriminant range and `#[repr]` attribute. +/// N.B.: `u128` values above `i128::MAX` will be treated as signed, but +/// that shouldn't affect anything, other than maybe debuginfo. +fn repr_discr( + dl: &TargetDataLayout, + repr: &ReprOptions, + min: i128, + max: i128, +) -> Result<(Integer, bool), LayoutError> { + // Theoretically, negative values could be larger in unsigned representation + // than the unsigned representation of the signed minimum. However, if there + // are any negative values, the only valid unsigned representation is u128 + // which can fit all i128 values, so the result remains unaffected. + let unsigned_fit = Integer::fit_unsigned(cmp::max(min as u128, max as u128)); + let signed_fit = cmp::max(Integer::fit_signed(min), Integer::fit_signed(max)); + + if let Some(ity) = repr.int { + let discr = Integer::from_attr(dl, ity); + let fit = if ity.is_signed() { signed_fit } else { unsigned_fit }; + if discr < fit { + return Err(LayoutError::UserError( + "Integer::repr_discr: `#[repr]` hint too small for \ + discriminant range of enum " + .to_string(), + )); + } + return Ok((discr, ity.is_signed())); + } + + let at_least = if repr.c() { + // This is usually I32, however it can be different on some platforms, + // notably hexagon and arm-none/thumb-none + dl.c_enum_min_size + } else { + // repr(Rust) enums try to be as small as possible + Integer::I8 + }; + + // If there are no negative values, we can use the unsigned fit. + Ok(if min >= 0 { + (cmp::max(unsigned_fit, at_least), false) + } else { + (cmp::max(signed_fit, at_least), true) + }) +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs index adfae0a1abb3..04b940afbe8f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs @@ -1,9 +1,8 @@ //! Target dependent parameters needed for layouts -use std::sync::Arc; - use base_db::CrateId; use hir_def::layout::TargetDataLayout; +use triomphe::Arc; use crate::db::HirDatabase; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs index a8971fde3c21..fca2e09ff0a9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs @@ -2,49 +2,55 @@ use base_db::fixture::WithFixture; use chalk_ir::{AdtId, TyKind}; -use hir_def::{ - db::DefDatabase, +use hir_def::db::DefDatabase; +use triomphe::Arc; + +use crate::{ + db::HirDatabase, layout::{Layout, LayoutError}, + test_db::TestDB, + Interner, Substitution, }; -use crate::{db::HirDatabase, test_db::TestDB, Interner, Substitution}; - -use super::layout_of_ty; +mod closure; fn current_machine_data_layout() -> String { project_model::target_data_layout::get(None, None, &HashMap::default()).unwrap() } -fn eval_goal(ra_fixture: &str, minicore: &str) -> Result { +fn eval_goal(ra_fixture: &str, minicore: &str) -> Result, LayoutError> { let target_data_layout = current_machine_data_layout(); let ra_fixture = format!( "{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\n{ra_fixture}", ); - let (db, file_id) = TestDB::with_single_file(&ra_fixture); - let module_id = db.module_for_file(file_id); - let def_map = module_id.def_map(&db); - let scope = &def_map[module_id.local_id].scope; - let adt_id = scope - .declarations() - .find_map(|x| match x { - hir_def::ModuleDefId::AdtId(x) => { - let name = match x { - hir_def::AdtId::StructId(x) => db.struct_data(x).name.to_smol_str(), - hir_def::AdtId::UnionId(x) => db.union_data(x).name.to_smol_str(), - hir_def::AdtId::EnumId(x) => db.enum_data(x).name.to_smol_str(), - }; - (name == "Goal").then_some(x) - } - _ => None, + let (db, file_ids) = TestDB::with_many_files(&ra_fixture); + let (adt_id, module_id) = file_ids + .into_iter() + .find_map(|file_id| { + let module_id = db.module_for_file(file_id); + let def_map = module_id.def_map(&db); + let scope = &def_map[module_id.local_id].scope; + let adt_id = scope.declarations().find_map(|x| match x { + hir_def::ModuleDefId::AdtId(x) => { + let name = match x { + hir_def::AdtId::StructId(x) => db.struct_data(x).name.to_smol_str(), + hir_def::AdtId::UnionId(x) => db.union_data(x).name.to_smol_str(), + hir_def::AdtId::EnumId(x) => db.enum_data(x).name.to_smol_str(), + }; + (name == "Goal").then_some(x) + } + _ => None, + })?; + Some((adt_id, module_id)) }) .unwrap(); let goal_ty = TyKind::Adt(AdtId(adt_id), Substitution::empty(Interner)).intern(Interner); - layout_of_ty(&db, &goal_ty, module_id.krate()) + db.layout_of_ty(goal_ty, module_id.krate()) } /// A version of `eval_goal` for types that can not be expressed in ADTs, like closures and `impl Trait` -fn eval_expr(ra_fixture: &str, minicore: &str) -> Result { +fn eval_expr(ra_fixture: &str, minicore: &str) -> Result, LayoutError> { let target_data_layout = current_machine_data_layout(); let ra_fixture = format!( "{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\nfn main(){{let goal = {{{ra_fixture}}};}}", @@ -68,7 +74,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result { let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0; let infer = db.infer(adt_id.into()); let goal_ty = infer.type_of_binding[b].clone(); - layout_of_ty(&db, &goal_ty, module_id.krate()) + db.layout_of_ty(goal_ty, module_id.krate()) } #[track_caller] @@ -81,8 +87,8 @@ fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) #[track_caller] fn check_size_and_align_expr(ra_fixture: &str, minicore: &str, size: u64, align: u64) { let l = eval_expr(ra_fixture, minicore).unwrap(); - assert_eq!(l.size.bytes(), size); - assert_eq!(l.align.abi.bytes(), align); + assert_eq!(l.size.bytes(), size, "size mismatch"); + assert_eq!(l.align.abi.bytes(), align, "align mismatch"); } #[track_caller] @@ -118,13 +124,31 @@ macro_rules! size_and_align { }; } +#[macro_export] macro_rules! size_and_align_expr { + (minicore: $($x:tt),*; stmts: [$($s:tt)*] $($t:tt)*) => { + { + #[allow(dead_code)] + #[allow(unused_must_use)] + #[allow(path_statements)] + { + $($s)* + let val = { $($t)* }; + $crate::layout::tests::check_size_and_align_expr( + &format!("{{ {} let val = {{ {} }}; val }}", stringify!($($s)*), stringify!($($t)*)), + &format!("//- minicore: {}\n", stringify!($($x),*)), + ::std::mem::size_of_val(&val) as u64, + ::std::mem::align_of_val(&val) as u64, + ); + } + } + }; ($($t:tt)*) => { { #[allow(dead_code)] { let val = { $($t)* }; - check_size_and_align_expr( + $crate::layout::tests::check_size_and_align_expr( stringify!($($t)*), "", ::std::mem::size_of_val(&val) as u64, @@ -196,6 +220,44 @@ struct X { } } +#[test] +fn associated_types() { + size_and_align! { + trait Tr { + type Ty; + } + + impl Tr for i32 { + type Ty = i64; + } + + struct Foo(::Ty); + struct Bar(A::Ty); + struct Goal(Foo, Bar, ::Ty); + } + check_size_and_align( + r#" +//- /b/mod.rs crate:b +pub trait Tr { + type Ty; +} +pub struct Foo(::Ty); + +//- /a/mod.rs crate:a deps:b +use b::{Tr, Foo}; + +struct S; +impl Tr for S { + type Ty = i64; +} +struct Goal(Foo); + "#, + "", + 8, + 8, + ); +} + #[test] fn return_position_impl_trait() { size_and_align_expr! { @@ -212,6 +274,45 @@ impl T for i64 {} fn foo() -> (impl T, impl T, impl T) { (2i64, 5i32, 7i32) } foo() } + size_and_align_expr! { + minicore: iterators; + stmts: [] + trait Tr {} + impl Tr for i32 {} + fn foo() -> impl Iterator { + [1, 2, 3].into_iter() + } + let mut iter = foo(); + let item = iter.next(); + (iter, item) + } + size_and_align_expr! { + minicore: future; + stmts: [] + use core::{future::Future, task::{Poll, Context}, pin::pin}; + use std::{task::Wake, sync::Arc}; + trait Tr {} + impl Tr for i32 {} + async fn f() -> impl Tr { + 2 + } + fn unwrap_fut(inp: impl Future) -> Poll { + // In a normal test we could use `loop {}` or `panic!()` here, + // but rustc actually runs this code. + let pinned = pin!(inp); + struct EmptyWaker; + impl Wake for EmptyWaker { + fn wake(self: Arc) { + } + } + let waker = Arc::new(EmptyWaker).into(); + let mut context = Context::from_waker(&waker); + let x = pinned.poll(&mut context); + x + } + let x = unwrap_fut(f()); + x + } size_and_align_expr! { struct Foo(T, T, (T, T)); trait T {} @@ -276,6 +377,14 @@ fn niche_optimization() { } } +#[test] +fn const_eval() { + size_and_align! { + const X: usize = 5; + struct Goal([i32; X]); + } +} + #[test] fn enums_with_discriminants() { size_and_align! { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs new file mode 100644 index 000000000000..576e7f3fc619 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs @@ -0,0 +1,257 @@ +use crate::size_and_align_expr; + +#[test] +fn zero_capture_simple() { + size_and_align_expr! { + |x: i32| x + 2 + } +} + +#[test] +fn move_simple() { + size_and_align_expr! { + minicore: copy; + stmts: [] + let y: i32 = 5; + move |x: i32| { + x + y + } + } +} + +#[test] +fn ref_simple() { + size_and_align_expr! { + minicore: copy; + stmts: [ + let y: i32 = 5; + ] + |x: i32| { + x + y + } + } + size_and_align_expr! { + minicore: copy; + stmts: [ + let mut y: i32 = 5; + ] + |x: i32| { + y = y + x; + y + } + } + size_and_align_expr! { + minicore: copy, deref_mut; + stmts: [ + let y: &mut i32 = &mut 5; + ] + |x: i32| { + *y += x; + } + } + size_and_align_expr! { + minicore: copy; + stmts: [ + struct X(i32, i64); + let x: X = X(2, 6); + ] + || { + x + } + } + size_and_align_expr! { + minicore: copy, deref_mut; + stmts: [ + struct X(i32, i64); + let x: &mut X = &mut X(2, 6); + ] + || { + (*x).0 as i64 + x.1 + } + } +} + +#[test] +fn ref_then_mut_then_move() { + size_and_align_expr! { + minicore: copy; + stmts: [ + struct X(i32, i64); + let mut x: X = X(2, 6); + ] + || { + &x; + &mut x; + x; + } + } +} + +#[test] +fn nested_closures() { + size_and_align_expr! { + || { + || { + || { + let x = 2; + move || { + move || { + x + } + } + } + } + } + } +} + +#[test] +fn capture_specific_fields2() { + size_and_align_expr! { + minicore: copy; + stmts: [ + let x = &mut 2; + ] + || { + *x = 5; + &x; + } + } +} + +#[test] +fn capture_specific_fields() { + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + move |x: i64| { + y.0 + x + (y.2 .0 as i64) + } + } + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + move |x: i64| { + let _ = &y; + y.0 + x + (y.2 .0 as i64) + } + } + size_and_align_expr! { + minicore: copy; + stmts: [ + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + ] + let y = &y; + move |x: i64| { + y.0 + x + (y.2 .0 as i64) + } + } + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + move |x: i64| { + let X(a, _, (b, _)) = y; + a + x + (b as i64) + } + } + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y = &&X(2, 5, (7, 3)); + move |x: i64| { + let X(a, _, (b, _)) = y; + *a + x + (*b as i64) + } + } + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + move |x: i64| { + match y { + X(a, _, (b, _)) => a + x + (b as i64), + } + } + } + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + move |x: i64| { + let X(a @ 2, _, (b, _)) = y else { return 5 }; + a + x + (b as i64) + } + } +} + +#[test] +fn match_pattern() { + size_and_align_expr! { + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + move |x: i64| { + match y { + _ => x, + } + } + } + size_and_align_expr! { + minicore: copy; + stmts: [ + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + ] + |x: i64| { + match y { + X(_a, _, _c) => x, + } + } + } + size_and_align_expr! { + minicore: copy; + stmts: [ + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + ] + |x: i64| { + match y { + _y => x, + } + } + } + size_and_align_expr! { + minicore: copy; + stmts: [ + struct X(i64, i32, (u8, i128)); + let y: X = X(2, 5, (7, 3)); + ] + |x: i64| { + match y { + ref _y => x, + } + } + } +} + +#[test] +fn ellipsis_pattern() { + size_and_align_expr! { + struct X(i8, u16, i32, u64, i128, u8); + let y: X = X(1, 2, 3, 4, 5, 6); + move |_: i64| { + let X(_a, .., _b, _c) = y; + } + } + size_and_align_expr! { + struct X { a: i32, b: u8, c: i128} + let y: X = X { a: 1, b: 2, c: 3 }; + move |_: i64| { + let X { a, b, .. } = y; + _ = (a, b); + } + } + size_and_align_expr! { + let y: (&&&(i8, u16, i32, u64, i128, u8), u16, i32, u64, i128, u8) = (&&&(1, 2, 3, 4, 5, 6), 2, 3, 4, 5, 6); + move |_: i64| { + let ((_a, .., _b, _c), .., _e, _f) = y; + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 9c63d67ab19a..1a4d003bf5e9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -1,6 +1,5 @@ //! The type system. We currently use this to infer types for completion, hover //! information and various assists. - #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] #[allow(unused)] @@ -8,12 +7,9 @@ macro_rules! eprintln { ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; } -mod autoderef; mod builder; mod chalk_db; mod chalk_ext; -pub mod consteval; -pub mod mir; mod infer; mod inhabitedness; mod interner; @@ -21,21 +17,28 @@ macro_rules! eprintln { mod mapping; mod tls; mod utils; + +pub mod autoderef; +pub mod consteval; pub mod db; pub mod diagnostics; pub mod display; +pub mod lang_items; +pub mod layout; pub mod method_resolution; +pub mod mir; pub mod primitive; pub mod traits; -pub mod layout; -pub mod lang_items; #[cfg(test)] mod tests; #[cfg(test)] mod test_db; -use std::{collections::HashMap, hash::Hash, sync::Arc}; +use std::{ + collections::{hash_map::Entry, HashMap}, + hash::Hash, +}; use chalk_ir::{ fold::{Shift, TypeFoldable}, @@ -44,12 +47,13 @@ macro_rules! eprintln { NoSolution, TyData, }; use either::Either; -use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId}; +use hir_def::{hir::ExprId, type_ref::Rawness, GeneralConstId, TypeOrConstParamId}; use hir_expand::name; use la_arena::{Arena, Idx}; -use mir::MirEvalError; +use mir::{MirEvalError, VTableMap}; use rustc_hash::FxHashSet; use traits::FnTrait; +use triomphe::Arc; use utils::Generics; use crate::{ @@ -60,6 +64,7 @@ macro_rules! eprintln { pub use builder::{ParamKind, TyBuilder}; pub use chalk_ext::*; pub use infer::{ + closure::{CaptureKind, CapturedItem}, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast, }; @@ -148,14 +153,26 @@ macro_rules! eprintln { pub type WhereClause = chalk_ir::WhereClause; /// A constant can have reference to other things. Memory map job is holding -/// the neccessary bits of memory of the const eval session to keep the constant +/// the necessary bits of memory of the const eval session to keep the constant /// meaningful. #[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct MemoryMap(pub HashMap>); +pub struct MemoryMap { + pub memory: HashMap>, + pub vtable: VTableMap, +} impl MemoryMap { fn insert(&mut self, addr: usize, x: Vec) { - self.0.insert(addr, x); + match self.memory.entry(addr) { + Entry::Occupied(mut e) => { + if e.get().len() < x.len() { + e.insert(x); + } + } + Entry::Vacant(e) => { + e.insert(x); + } + } } /// This functions convert each address by a function `f` which gets the byte intervals and assign an address @@ -165,7 +182,15 @@ fn transform_addresses( &self, mut f: impl FnMut(&[u8]) -> Result, ) -> Result, MirEvalError> { - self.0.iter().map(|x| Ok((*x.0, f(x.1)?))).collect() + self.memory.iter().map(|x| Ok((*x.0, f(x.1)?))).collect() + } + + fn get<'a>(&'a self, addr: usize, size: usize) -> Option<&'a [u8]> { + if size == 0 { + Some(&[]) + } else { + self.memory.get(&addr)?.get(0..size) + } } } @@ -173,6 +198,9 @@ fn transform_addresses( #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConstScalar { Bytes(Vec, MemoryMap), + // FIXME: this is a hack to get around chalk not being able to represent unevaluatable + // constants + UnevaluatedConst(GeneralConstId, Substitution), /// Case of an unknown value that rustc might know but we don't // FIXME: this is a hack to get around chalk not being able to represent unevaluatable // constants @@ -283,16 +311,19 @@ pub fn from_params_and_return( pub fn from_fn_ptr(fn_ptr: &FnPointer) -> CallableSig { CallableSig { // FIXME: what to do about lifetime params? -> return PolyFnSig - params_and_return: fn_ptr - .substitution - .clone() - .shifted_out_to(Interner, DebruijnIndex::ONE) - .expect("unexpected lifetime vars in fn ptr") - .0 - .as_slice(Interner) - .iter() - .map(|arg| arg.assert_ty_ref(Interner).clone()) - .collect(), + // FIXME: use `Arc::from_iter` when it becomes available + params_and_return: Arc::from( + fn_ptr + .substitution + .clone() + .shifted_out_to(Interner, DebruijnIndex::ONE) + .expect("unexpected lifetime vars in fn ptr") + .0 + .as_slice(Interner) + .iter() + .map(|arg| arg.assert_ty_ref(Interner).clone()) + .collect::>(), + ), is_varargs: fn_ptr.sig.variadic, safety: fn_ptr.sig.safety, } @@ -576,15 +607,19 @@ fn try_fold_free_var_lifetime( } pub fn callable_sig_from_fnonce( - self_ty: &Ty, + mut self_ty: &Ty, env: Arc, db: &dyn HirDatabase, ) -> Option { + if let Some((ty, _, _)) = self_ty.as_reference() { + // This will happen when it implements fn or fn mut, since we add a autoborrow adjustment + self_ty = ty; + } let krate = env.krate; let fn_once_trait = FnTrait::FnOnce.get_id(db, krate)?; let output_assoc_type = db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?; - let mut table = InferenceTable::new(db, env.clone()); + let mut table = InferenceTable::new(db, env); let b = TyBuilder::trait_ref(db, fn_once_trait); if b.remaining() != 2 { return None; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 23b15087e316..0c68891fe493 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -8,7 +8,6 @@ use std::{ cell::{Cell, RefCell, RefMut}, iter, - sync::Arc, }; use base_db::CrateId; @@ -18,19 +17,20 @@ use either::Either; use hir_def::{ - adt::StructKind, - body::{Expander, LowerCtx}, builtin_type::BuiltinType, + data::adt::StructKind, + expander::Expander, generics::{ TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget, }, lang_item::{lang_attr, LangItem}, - path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments}, + nameres::MacroSubNs, + path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{ConstRefOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef}, - AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId, - HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, StructId, - TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId, + AdtId, AssocItemId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, + GenericDefId, HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, + StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId, }; use hir_expand::{name::Name, ExpandResult}; use intern::Interned; @@ -39,6 +39,7 @@ use smallvec::SmallVec; use stdx::{impl_from, never}; use syntax::ast; +use triomphe::Arc; use crate::{ all_super_traits, @@ -103,7 +104,7 @@ fn swap(&self, impl_trait_mode: &Self) { #[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, - pub resolver: &'a Resolver, + resolver: &'a Resolver, in_binders: DebruijnIndex, /// Note: Conceptually, it's thinkable that we could be in a location where /// some type params should be represented as placeholders, and others @@ -378,10 +379,19 @@ pub fn lower_ty_ext(&self, type_ref: &TypeRef) -> (Ty, Option) { }; let ty = { let macro_call = macro_call.to_node(self.db.upcast()); - match expander.enter_expand::(self.db.upcast(), macro_call) { + let resolver = |path| { + self.resolver.resolve_path_as_macro( + self.db.upcast(), + &path, + Some(MacroSubNs::Bang), + ) + }; + match expander.enter_expand::(self.db.upcast(), macro_call, resolver) + { Ok(ExpandResult { value: Some((mark, expanded)), .. }) => { - let ctx = LowerCtx::new(self.db.upcast(), expander.current_file_id()); - let type_ref = TypeRef::from_ast(&ctx, expanded); + let ctx = expander.ctx(self.db.upcast()); + // FIXME: Report syntax errors in expansion here + let type_ref = TypeRef::from_ast(&ctx, expanded.tree()); drop(expander); let ty = self.lower_ty(&type_ref); @@ -425,11 +435,10 @@ fn lower_ty_only_param(&self, type_ref: &TypeRef) -> Option if path.segments().len() > 1 { return None; } - let resolution = - match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { - Some((it, None)) => it, - _ => return None, - }; + let resolution = match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { + Some((it, None)) => it, + _ => return None, + }; match resolution { TypeNs::GenericParam(param_id) => Some(param_id.into()), _ => None, @@ -608,7 +617,7 @@ pub(crate) fn lower_path(&self, path: &Path) -> (Ty, Option) { } let (resolution, remaining_index) = - match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { Some(it) => it, None => return (TyKind::Error.intern(Interner), None), }; @@ -716,7 +725,7 @@ pub(super) fn substs_from_path( resolved: ValueTyDefId, infer_args: bool, ) -> Substitution { - let last = path.segments().last().expect("path should have at least one segment"); + let last = path.segments().last(); let (segment, generic_def) = match resolved { ValueTyDefId::FunctionId(it) => (last, Some(it.into())), ValueTyDefId::StructId(it) => (last, Some(it.into())), @@ -732,13 +741,20 @@ pub(super) fn substs_from_path( let len = path.segments().len(); let penultimate = len.checked_sub(2).and_then(|idx| path.segments().get(idx)); let segment = match penultimate { - Some(segment) if segment.args_and_bindings.is_some() => segment, + Some(segment) if segment.args_and_bindings.is_some() => Some(segment), _ => last, }; (segment, Some(var.parent.into())) } }; - self.substs_from_path_segment(segment, generic_def, infer_args, None) + if let Some(segment) = segment { + self.substs_from_path_segment(segment, generic_def, infer_args, None) + } else if let Some(generic_def) = generic_def { + // lang item + self.substs_from_args_and_bindings(None, Some(generic_def), infer_args, None) + } else { + Substitution::empty(Interner) + } } fn substs_from_path_segment( @@ -747,6 +763,21 @@ fn substs_from_path_segment( def: Option, infer_args: bool, explicit_self_ty: Option, + ) -> Substitution { + self.substs_from_args_and_bindings( + segment.args_and_bindings, + def, + infer_args, + explicit_self_ty, + ) + } + + fn substs_from_args_and_bindings( + &self, + args_and_bindings: Option<&GenericArgs>, + 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(); @@ -780,7 +811,7 @@ fn substs_from_path_segment( }; let mut had_explicit_args = false; - if let Some(generic_args) = &segment.args_and_bindings { + if let Some(generic_args) = &args_and_bindings { if !generic_args.has_self_type { fill_self_params(); } @@ -879,12 +910,11 @@ fn lower_trait_ref_from_path( path: &Path, explicit_self_ty: Option, ) -> Option { - let resolved = - match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path.mod_path())? { - // FIXME(trait_alias): We need to handle trait alias here. - TypeNs::TraitId(tr) => tr, - _ => return None, - }; + let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? { + // FIXME(trait_alias): We need to handle trait alias here. + TypeNs::TraitId(tr) => tr, + _ => return None, + }; let segment = path.segments().last().expect("path should have at least one segment"); Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty)) } @@ -968,7 +998,7 @@ pub(crate) fn lower_type_bound( // ignore `T: Drop` or `T: Destruct` bounds. // - `T: ~const Drop` has a special meaning in Rust 1.61 that we don't implement. // (So ideally, we'd only ignore `~const Drop` here) - // - `Destruct` impls are built-in in 1.62 (current nightlies as of 08-04-2022), so until + // - `Destruct` impls are built-in in 1.62 (current nightly as of 08-04-2022), so until // the builtin impls are supported by Chalk, we ignore them here. if let Some(lang) = lang_attr(self.db.upcast(), tr.hir_trait_id()) { if matches!(lang, LangItem::Drop | LangItem::Destruct) { @@ -1062,23 +1092,23 @@ fn assoc_type_bindings_from_type_bound( associated_ty_id: to_assoc_type_id(associated_ty), substitution, }; - let mut preds: SmallVec<[_; 1]> = SmallVec::with_capacity( + let mut predicates: SmallVec<[_; 1]> = SmallVec::with_capacity( binding.type_ref.as_ref().map_or(0, |_| 1) + binding.bounds.len(), ); if let Some(type_ref) = &binding.type_ref { let ty = self.lower_ty(type_ref); let alias_eq = AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty }; - preds.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq))); + predicates.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq))); } for bound in binding.bounds.iter() { - preds.extend(self.lower_type_bound( + predicates.extend(self.lower_type_bound( bound, TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner), false, )); } - preds + predicates }) } @@ -1145,7 +1175,7 @@ fn lower_dyn_trait(&self, bounds: &[Interned]) -> Ty { return None; } - // As multiple occurrences of the same auto traits *are* permitted, we dedulicate the + // As multiple occurrences of the same auto traits *are* permitted, we deduplicate the // bounds. We shouldn't have repeated elements besides auto traits at this point. bounds.dedup(); @@ -1381,9 +1411,7 @@ pub(crate) fn generic_predicates_for_param_query( Some(it) => it, None => return true, }; - let tr = match resolver - .resolve_path_in_type_ns_fully(db.upcast(), path.mod_path()) - { + let tr = match resolver.resolve_path_in_type_ns_fully(db.upcast(), path) { Some(TypeNs::TraitId(tr)) => tr, _ => return false, }; @@ -1420,7 +1448,19 @@ pub(crate) fn generic_predicates_for_param_recover( _param_id: &TypeOrConstParamId, _assoc_name: &Option, ) -> Arc<[Binders]> { - Arc::new([]) + // FIXME: use `Arc::from_iter` when it becomes available + Arc::from(vec![]) +} + +pub(crate) fn trait_environment_for_body_query( + db: &dyn HirDatabase, + def: DefWithBodyId, +) -> Arc { + let Some(def) = def.as_generic_def_id() else { + let krate = def.module(db.upcast()).krate(); + return Arc::new(TraitEnvironment::empty(krate)); + }; + db.trait_environment(def) } pub(crate) fn trait_environment_query( @@ -1478,7 +1518,7 @@ pub(crate) fn trait_environment_query( let env = chalk_ir::Environment::new(Interner).add_clauses(Interner, clauses); - Arc::new(TraitEnvironment { krate, traits_from_clauses: traits_in_scope, env }) + Arc::new(TraitEnvironment { krate, block: None, traits_from_clauses: traits_in_scope, env }) } /// Resolve the where clause(s) of an item with generics. @@ -1547,30 +1587,33 @@ pub(crate) fn generic_defaults_query( let generic_params = generics(db.upcast(), def); let parent_start_idx = generic_params.len_self(); - let defaults = generic_params - .iter() - .enumerate() - .map(|(idx, (id, p))| { - let p = match p { - TypeOrConstParamData::TypeParamData(p) => p, - TypeOrConstParamData::ConstParamData(_) => { - // FIXME: implement const generic defaults - let val = unknown_const_as_generic( - db.const_param_ty(ConstParamId::from_unchecked(id)), - ); - 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)); + let defaults = Arc::from( + generic_params + .iter() + .enumerate() + .map(|(idx, (id, p))| { + let p = match p { + TypeOrConstParamData::TypeParamData(p) => p, + TypeOrConstParamData::ConstParamData(_) => { + // FIXME: implement const generic defaults + let val = unknown_const_as_generic( + db.const_param_ty(ConstParamId::from_unchecked(id)), + ); + 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 is forbidden (FIXME: report diagnostic) - ty = fallback_bound_vars(ty, idx, parent_start_idx); - crate::make_binders(db, &generic_params, ty.cast(Interner)) - }) - .collect(); + // Each default can only refer to previous parameters. + // 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)) + }) + // FIXME: use `Arc::from_iter` when it becomes available + .collect::>(), + ); defaults } @@ -1583,18 +1626,21 @@ pub(crate) fn generic_defaults_recover( let generic_params = generics(db.upcast(), *def); // FIXME: this code is not covered in tests. // we still need one default per parameter - let defaults = generic_params - .iter_id() - .map(|id| { - let val = match id { - Either::Left(_) => { - GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) - } - Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)), - }; - crate::make_binders(db, &generic_params, val) - }) - .collect(); + let defaults = Arc::from( + generic_params + .iter_id() + .map(|id| { + let val = match id { + Either::Left(_) => { + GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) + } + Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)), + }; + crate::make_binders(db, &generic_params, val) + }) + // FIXME: use `Arc::from_iter` when it becomes available + .collect::>(), + ); defaults } @@ -1605,7 +1651,7 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { let ctx_params = TyLoweringContext::new(db, &resolver) .with_impl_trait_mode(ImplTraitLoweringMode::Variable) .with_type_param_mode(ParamLoweringMode::Variable); - let params = data.params.iter().map(|(_, tr)| ctx_params.lower_ty(tr)).collect::>(); + let params = data.params.iter().map(|tr| ctx_params.lower_ty(tr)).collect::>(); let ctx_ret = TyLoweringContext::new(db, &resolver) .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) .with_type_param_mode(ParamLoweringMode::Variable); @@ -1948,7 +1994,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>( // as types. Maybe here is not the best place to do it, but // it works. if let TypeRef::Path(p) = t { - let p = p.mod_path(); + let p = p.mod_path()?; if p.kind == PathKind::Plain { if let [n] = p.segments() { let c = ConstRefOrPath::Path(n.clone()); @@ -1977,8 +2023,16 @@ pub(crate) fn const_or_path_to_chalk( ConstRefOrPath::Scalar(s) => intern_const_ref(db, s, expected_ty, resolver.krate()), ConstRefOrPath::Path(n) => { let path = ModPath::from_segments(PathKind::Plain, Some(n.clone())); - path_to_const(db, resolver, &path, mode, args, debruijn) - .unwrap_or_else(|| unknown_const(expected_ty)) + path_to_const( + db, + resolver, + &Path::from_known_path_with_no_generic(path), + mode, + args, + debruijn, + expected_ty.clone(), + ) + .unwrap_or_else(|| unknown_const(expected_ty)) } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index f3a27632bf54..6fa3d1351a91 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -2,25 +2,28 @@ //! 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 rustc_hir_analysis/check/method/probe.rs. -use std::{ops::ControlFlow, sync::Arc}; +use std::ops::ControlFlow; use base_db::{CrateId, Edition}; -use chalk_ir::{cast::Cast, Mutability, TyKind, UniverseIndex}; +use chalk_ir::{cast::Cast, Mutability, TyKind, UniverseIndex, WhereClause}; use hir_def::{ - data::ImplData, item_scope::ItemScope, lang_item::LangItem, nameres::DefMap, AssocItemId, - BlockId, ConstId, FunctionId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, - ModuleId, TraitId, + data::{adt::StructFlags, ImplData}, + item_scope::ItemScope, + nameres::DefMap, + AssocItemId, BlockId, ConstId, FunctionId, HasModule, ImplId, ItemContainerId, Lookup, + ModuleDefId, ModuleId, TraitId, }; use hir_expand::name::Name; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; use stdx::never; +use triomphe::Arc; use crate::{ autoderef::{self, AutoderefKind}, db::HirDatabase, from_chalk_trait_id, from_foreign_def_id, - infer::{unify::InferenceTable, Adjust, Adjustment, AutoBorrow, OverloadedDeref, PointerCast}, + infer::{unify::InferenceTable, Adjust, Adjustment, OverloadedDeref, PointerCast}, primitive::{FloatTy, IntTy, UintTy}, static_lifetime, to_chalk_trait_id, utils::all_super_traits, @@ -147,31 +150,30 @@ pub(crate) fn trait_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) - Arc::new(impls) } - pub(crate) fn trait_impls_in_block_query( - db: &dyn HirDatabase, - block: BlockId, - ) -> Option> { + pub(crate) fn trait_impls_in_block_query(db: &dyn HirDatabase, block: BlockId) -> Arc { let _p = profile::span("trait_impls_in_block_query"); let mut impls = Self { map: FxHashMap::default() }; - let block_def_map = db.block_def_map(block)?; + let block_def_map = db.block_def_map(block); impls.collect_def_map(db, &block_def_map); impls.shrink_to_fit(); - Some(Arc::new(impls)) + Arc::new(impls) } - pub(crate) fn trait_impls_in_deps_query(db: &dyn HirDatabase, krate: CrateId) -> Arc { + pub(crate) fn trait_impls_in_deps_query( + db: &dyn HirDatabase, + krate: CrateId, + ) -> Arc<[Arc]> { let _p = profile::span("trait_impls_in_deps_query").detail(|| format!("{krate:?}")); let crate_graph = db.crate_graph(); - let mut res = Self { map: FxHashMap::default() }; - - for krate in crate_graph.transitive_deps(krate) { - res.merge(&db.trait_impls_in_crate(krate)); - } - res.shrink_to_fit(); - - Arc::new(res) + // FIXME: use `Arc::from_iter` when it becomes available + Arc::from( + crate_graph + .transitive_deps(krate) + .map(|krate| db.trait_impls_in_crate(krate)) + .collect::>(), + ) } fn shrink_to_fit(&mut self) { @@ -185,6 +187,15 @@ fn shrink_to_fit(&mut self) { fn collect_def_map(&mut self, db: &dyn HirDatabase, def_map: &DefMap) { for (_module_id, module_data) in def_map.modules() { for impl_id in module_data.scope.impls() { + // Reservation impls should be ignored during trait resolution, so we never need + // them during type analysis. See rust-lang/rust#64631 for details. + // + // FIXME: Reservation impls should be considered during coherence checks. If we are + // (ever) to implement coherence checks, this filtering should be done by the trait + // solver. + if db.attrs(impl_id.into()).by_key("rustc_reservation_impl").exists() { + continue; + } let target_trait = match db.impl_trait(impl_id) { Some(tr) => tr.skip_binders().hir_trait_id(), None => continue, @@ -210,15 +221,6 @@ fn collect_def_map(&mut self, db: &dyn HirDatabase, def_map: &DefMap) { } } - fn merge(&mut self, other: &Self) { - for (trait_, other_map) in &other.map { - let map = self.map.entry(*trait_).or_default(); - for (fp, impls) in other_map { - map.entry(*fp).or_default().extend(impls); - } - } - } - /// Queries all trait impls for the given type. pub fn for_self_ty_without_blanket_impls( &self, @@ -271,6 +273,7 @@ pub struct InherentImpls { impl InherentImpls { pub(crate) fn inherent_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) -> Arc { + let _p = profile::span("inherent_impls_in_crate_query").detail(|| format!("{krate:?}")); let mut impls = Self { map: FxHashMap::default(), invalid_impls: Vec::default() }; let crate_def_map = db.crate_def_map(krate); @@ -280,17 +283,15 @@ pub(crate) fn inherent_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId Arc::new(impls) } - pub(crate) fn inherent_impls_in_block_query( - db: &dyn HirDatabase, - block: BlockId, - ) -> Option> { + pub(crate) fn inherent_impls_in_block_query(db: &dyn HirDatabase, block: BlockId) -> Arc { + let _p = profile::span("inherent_impls_in_block_query"); let mut impls = Self { map: FxHashMap::default(), invalid_impls: Vec::default() }; - if let Some(block_def_map) = db.block_def_map(block) { - impls.collect_def_map(db, &block_def_map); - impls.shrink_to_fit(); - return Some(Arc::new(impls)); - } - None + + let block_def_map = db.block_def_map(block); + impls.collect_def_map(db, &block_def_map); + impls.shrink_to_fit(); + + Arc::new(impls) } fn shrink_to_fit(&mut self) { @@ -404,12 +405,14 @@ pub fn def_crates( match ty.kind(Interner) { &TyKind::Adt(AdtId(def_id), _) => { let rustc_has_incoherent_inherent_impls = match def_id { - hir_def::AdtId::StructId(id) => { - db.struct_data(id).rustc_has_incoherent_inherent_impls - } - hir_def::AdtId::UnionId(id) => { - db.union_data(id).rustc_has_incoherent_inherent_impls - } + hir_def::AdtId::StructId(id) => db + .struct_data(id) + .flags + .contains(StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL), + hir_def::AdtId::UnionId(id) => db + .union_data(id) + .flags + .contains(StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL), hir_def::AdtId::EnumId(id) => db.enum_data(id).rustc_has_incoherent_inherent_impls, }; Some(if rustc_has_incoherent_inherent_impls { @@ -449,55 +452,6 @@ pub fn def_crates( } } -pub fn lang_items_for_bin_op(op: syntax::ast::BinaryOp) -> Option<(Name, LangItem)> { - use hir_expand::name; - use syntax::ast::{ArithOp, BinaryOp, CmpOp, Ordering}; - Some(match op { - BinaryOp::LogicOp(_) => return None, - BinaryOp::ArithOp(aop) => match aop { - ArithOp::Add => (name![add], LangItem::Add), - ArithOp::Mul => (name![mul], LangItem::Mul), - ArithOp::Sub => (name![sub], LangItem::Sub), - ArithOp::Div => (name![div], LangItem::Div), - ArithOp::Rem => (name![rem], LangItem::Rem), - ArithOp::Shl => (name![shl], LangItem::Shl), - ArithOp::Shr => (name![shr], LangItem::Shr), - ArithOp::BitXor => (name![bitxor], LangItem::BitXor), - ArithOp::BitOr => (name![bitor], LangItem::BitOr), - ArithOp::BitAnd => (name![bitand], LangItem::BitAnd), - }, - BinaryOp::Assignment { op: Some(aop) } => match aop { - ArithOp::Add => (name![add_assign], LangItem::AddAssign), - ArithOp::Mul => (name![mul_assign], LangItem::MulAssign), - ArithOp::Sub => (name![sub_assign], LangItem::SubAssign), - ArithOp::Div => (name![div_assign], LangItem::DivAssign), - ArithOp::Rem => (name![rem_assign], LangItem::RemAssign), - ArithOp::Shl => (name![shl_assign], LangItem::ShlAssign), - ArithOp::Shr => (name![shr_assign], LangItem::ShrAssign), - ArithOp::BitXor => (name![bitxor_assign], LangItem::BitXorAssign), - ArithOp::BitOr => (name![bitor_assign], LangItem::BitOrAssign), - ArithOp::BitAnd => (name![bitand_assign], LangItem::BitAndAssign), - }, - BinaryOp::CmpOp(cop) => match cop { - CmpOp::Eq { negated: false } => (name![eq], LangItem::PartialEq), - CmpOp::Eq { negated: true } => (name![ne], LangItem::PartialEq), - CmpOp::Ord { ordering: Ordering::Less, strict: false } => { - (name![le], LangItem::PartialOrd) - } - CmpOp::Ord { ordering: Ordering::Less, strict: true } => { - (name![lt], LangItem::PartialOrd) - } - CmpOp::Ord { ordering: Ordering::Greater, strict: false } => { - (name![ge], LangItem::PartialOrd) - } - CmpOp::Ord { ordering: Ordering::Greater, strict: true } => { - (name![gt], LangItem::PartialOrd) - } - }, - BinaryOp::Assignment { op: None } => return None, - }) -} - /// Look up the method with the given name. pub(crate) fn lookup_method( db: &dyn HirDatabase, @@ -600,9 +554,9 @@ pub(crate) fn apply(&self, table: &mut InferenceTable<'_>, ty: Ty) -> (Ty, Vec for dyn Foo<5>`, so if the trait id is equal, no matter + // what the generics are, we are sure that the method is come from the vtable. + WhereClause::Implemented(tr) => tr.trait_id == trait_ref.trait_id, + _ => false, + }); + if is_my_trait_in_bounds { + return Some(fn_params); + } + } + None +} + /// Looks up the impl method that actually runs for the trait method `func`. /// /// Returns `func` if it's not a method defined in a trait or the lookup failed. @@ -701,9 +688,8 @@ pub fn lookup_impl_method( func: FunctionId, fn_subst: Substitution, ) -> (FunctionId, Substitution) { - let trait_id = match func.lookup(db.upcast()).container { - ItemContainerId::TraitId(id) => id, - _ => return (func, fn_subst), + let ItemContainerId::TraitId(trait_id) = func.lookup(db.upcast()).container else { + return (func, fn_subst) }; let trait_params = db.generic_params(trait_id.into()).type_or_consts.len(); let fn_params = fn_subst.len(Interner) - trait_params; @@ -713,7 +699,7 @@ pub fn lookup_impl_method( }; let name = &db.function_data(func).name; - lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name) + let Some((impl_fn, impl_subst)) = lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name) .and_then(|assoc| { if let (AssocItemId::FunctionId(id), subst) = assoc { Some((id, subst)) @@ -721,7 +707,16 @@ pub fn lookup_impl_method( None } }) - .unwrap_or((func, fn_subst)) + else { + return (func, fn_subst); + }; + ( + impl_fn, + Substitution::from_iter( + Interner, + fn_subst.iter(Interner).take(fn_params).chain(impl_subst.iter(Interner)), + ), + ) } fn lookup_impl_assoc_item_for_trait_ref( @@ -730,10 +725,20 @@ fn lookup_impl_assoc_item_for_trait_ref( env: Arc, name: &Name, ) -> Option<(AssocItemId, Substitution)> { + let hir_trait_id = trait_ref.hir_trait_id(); let self_ty = trait_ref.self_type_parameter(Interner); let self_ty_fp = TyFingerprint::for_trait_impl(&self_ty)?; let impls = db.trait_impls_in_deps(env.krate); - let impls = impls.for_trait_and_self_ty(trait_ref.hir_trait_id(), self_ty_fp); + let self_impls = match self_ty.kind(Interner) { + TyKind::Adt(id, _) => { + id.0.module(db.upcast()).containing_block().map(|x| db.trait_impls_in_block(x)) + } + _ => None, + }; + let impls = impls + .iter() + .chain(self_impls.as_ref()) + .flat_map(|impls| impls.for_trait_and_self_ty(hir_trait_id, self_ty_fp)); let table = InferenceTable::new(db, env); @@ -759,9 +764,8 @@ fn find_matching_impl( actual_trait_ref: TraitRef, ) -> Option<(Arc, Substitution)> { let db = table.db; - loop { - let impl_ = impls.next()?; - let r = table.run_in_snapshot(|table| { + impls.find_map(|impl_| { + table.run_in_snapshot(|table| { let impl_data = db.impl_data(impl_); let impl_substs = TyBuilder::subst_for_def(db, impl_, None).fill_with_inference_vars(table).build(); @@ -778,12 +782,11 @@ fn find_matching_impl( .into_iter() .map(|b| b.cast(Interner)); let goal = crate::Goal::all(Interner, wcs); - table.try_obligation(goal).map(|_| (impl_data, table.resolve_completely(impl_substs))) - }); - if r.is_some() { - break r; - } - } + table.try_obligation(goal.clone())?; + table.register_obligation(goal); + Some((impl_data, table.resolve_completely(impl_substs))) + }) + }) } fn is_inherent_impl_coherent( @@ -824,12 +827,14 @@ fn is_inherent_impl_coherent( | TyKind::Scalar(_) => true, &TyKind::Adt(AdtId(adt), _) => match adt { - hir_def::AdtId::StructId(it) => { - db.struct_data(it).rustc_has_incoherent_inherent_impls - } - hir_def::AdtId::UnionId(it) => { - db.union_data(it).rustc_has_incoherent_inherent_impls - } + hir_def::AdtId::StructId(id) => db + .struct_data(id) + .flags + .contains(StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL), + hir_def::AdtId::UnionId(id) => db + .union_data(id) + .flags + .contains(StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL), hir_def::AdtId::EnumId(it) => db.enum_data(it).rustc_has_incoherent_inherent_impls, }, TyKind::Dyn(it) => it.principal().map_or(false, |trait_ref| { @@ -963,7 +968,14 @@ fn iterate_method_candidates_with_autoref( ) }; - iterate_method_candidates_by_receiver(receiver_ty, first_adjustment.clone())?; + let mut maybe_reborrowed = first_adjustment.clone(); + if let Some((_, _, m)) = receiver_ty.value.as_reference() { + // Prefer reborrow of references to move + maybe_reborrowed.autoref = Some(m); + maybe_reborrowed.autoderefs += 1; + } + + iterate_method_candidates_by_receiver(receiver_ty, maybe_reborrowed)?; let refed = Canonical { value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone()) @@ -1108,7 +1120,7 @@ fn iterate_trait_method_candidates( }; if !known_implemented { let goal = generic_implements_goal(db, env.clone(), t, &canonical_self_ty); - if db.trait_solve(env.krate, goal.cast(Interner)).is_none() { + if db.trait_solve(env.krate, env.block, goal.cast(Interner)).is_none() { continue 'traits; } } @@ -1180,23 +1192,19 @@ fn iterate_inherent_methods( }; while let Some(block_id) = block { - if let Some(impls) = db.inherent_impls_in_block(block_id) { - impls_for_self_ty( - &impls, - self_ty, - table, - name, - receiver_ty, - receiver_adjustments.clone(), - module, - callback, - )?; - } + let impls = db.inherent_impls_in_block(block_id); + impls_for_self_ty( + &impls, + self_ty, + table, + name, + receiver_ty, + receiver_adjustments.clone(), + module, + callback, + )?; - block = db - .block_def_map(block_id) - .and_then(|map| map.parent()) - .and_then(|module| module.containing_block()); + block = db.block_def_map(block_id).parent().and_then(|module| module.containing_block()); } for krate in def_crates { @@ -1274,7 +1282,7 @@ fn impls_for_self_ty( } /// Returns the receiver type for the index trait call. -pub fn resolve_indexing_op( +pub(crate) fn resolve_indexing_op( db: &dyn HirDatabase, env: Arc, ty: Canonical, @@ -1284,8 +1292,11 @@ pub fn resolve_indexing_op( let ty = table.instantiate_canonical(ty); let deref_chain = autoderef_method_receiver(&mut table, ty); for (ty, adj) in deref_chain { - let goal = generic_implements_goal(db, env.clone(), index_trait, &ty); - if db.trait_solve(env.krate, goal.cast(Interner)).is_some() { + let goal = generic_implements_goal(db, table.trait_env.clone(), index_trait, &ty); + if db + .trait_solve(table.trait_env.krate, table.trait_env.block, goal.cast(Interner)) + .is_some() + { return Some(adj); } } @@ -1310,14 +1321,12 @@ fn is_valid_candidate( ) -> IsValidCandidate { let db = table.db; match item { - AssocItemId::FunctionId(m) => { - is_valid_fn_candidate(table, m, name, receiver_ty, self_ty, visible_from_module) + AssocItemId::FunctionId(f) => { + is_valid_fn_candidate(table, f, name, receiver_ty, self_ty, visible_from_module) } AssocItemId::ConstId(c) => { - let data = db.const_data(c); check_that!(receiver_ty.is_none()); - - check_that!(name.map_or(true, |n| data.name.as_ref() == Some(n))); + check_that!(name.map_or(true, |n| db.const_data(c).name.as_ref() == Some(n))); if let Some(from_module) = visible_from_module { if !db.const_visibility(c).is_visible_from(db.upcast(), from_module) { @@ -1441,7 +1450,7 @@ pub fn implements_trait( trait_: TraitId, ) -> bool { let goal = generic_implements_goal(db, env.clone(), trait_, ty); - let solution = db.trait_solve(env.krate, goal.cast(Interner)); + let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner)); solution.is_some() } @@ -1453,7 +1462,7 @@ pub fn implements_trait_unique( trait_: TraitId, ) -> bool { let goal = generic_implements_goal(db, env.clone(), trait_, ty); - let solution = db.trait_solve(env.krate, goal.cast(Interner)); + let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner)); matches!(solution, Some(crate::Solution::Unique(_))) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index 7c1cbbdf53d2..2345bab0bb4d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -3,12 +3,15 @@ use std::{fmt::Display, iter}; use crate::{ - infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty, + consteval::usize_const, db::HirDatabase, display::HirDisplay, infer::PointerCast, + lang_items::is_box, mapping::ToChalk, CallableDefId, ClosureId, Const, ConstScalar, + InferenceResult, Interner, MemoryMap, Substitution, Ty, TyKind, }; +use base_db::CrateId; use chalk_ir::Mutability; use hir_def::{ - expr::{BindingId, Expr, ExprId, Ordering, PatId}, - DefWithBodyId, FieldId, UnionId, VariantId, + hir::{BindingId, Expr, ExprId, Ordering, PatId}, + DefWithBodyId, FieldId, StaticId, UnionId, VariantId, }; use la_arena::{Arena, ArenaMap, Idx, RawIdx}; @@ -16,12 +19,19 @@ mod lower; mod borrowck; mod pretty; +mod monomorphization; pub use borrowck::{borrowck_query, BorrowckResult, MutabilityReason}; -pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError}; -pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError}; +pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError, VTableMap}; +pub use lower::{ + lower_to_mir, mir_body_for_closure_query, mir_body_query, mir_body_recover, MirLowerError, +}; +pub use monomorphization::{ + monomorphize_mir_body_bad, monomorphized_mir_body_for_closure_query, + monomorphized_mir_body_query, monomorphized_mir_body_recover, +}; use smallvec::{smallvec, SmallVec}; -use stdx::impl_from; +use stdx::{impl_from, never}; use super::consteval::{intern_const_scalar, try_const_usize}; @@ -32,7 +42,7 @@ fn return_slot() -> LocalId { LocalId::from_raw(RawIdx::from(0)) } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Local { pub ty: Ty, } @@ -52,7 +62,7 @@ pub struct Local { /// This is what is implemented in miri today. Are these the semantics we want for MIR? Is this /// something we can even decide without knowing more about Rust's memory model? /// -/// **Needs clarifiation:** Is loading a place that has its variant index set well-formed? Miri +/// **Needs clarification:** Is loading a place that has its variant index set well-formed? Miri /// currently implements it, but it seems like this may be something to check against in the /// validator. #[derive(Debug, PartialEq, Eq, Clone)] @@ -73,6 +83,9 @@ pub enum Operand { Move(Place), /// Constants are already semantically values, and remain unchanged. Constant(Const), + /// NON STANDARD: This kind of operand returns an immutable reference to that static memory. Rustc + /// handles it with the `Constant` variant somehow. + Static(StaticId), } impl Operand { @@ -87,31 +100,141 @@ fn from_bytes(data: Vec, ty: Ty) -> Self { fn const_zst(ty: Ty) -> Operand { Self::from_bytes(vec![], ty) } + + fn from_fn( + db: &dyn HirDatabase, + func_id: hir_def::FunctionId, + generic_args: Substitution, + ) -> Operand { + let ty = + chalk_ir::TyKind::FnDef(CallableDefId::FunctionId(func_id).to_chalk(db), generic_args) + .intern(Interner); + Operand::from_bytes(vec![], ty) + } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ProjectionElem { Deref, Field(FieldId), - TupleField(usize), + // FIXME: get rid of this, and use FieldId for tuples and closures + TupleOrClosureField(usize), Index(V), - ConstantIndex { offset: u64, min_length: u64, from_end: bool }, - Subslice { from: u64, to: u64, from_end: bool }, + ConstantIndex { offset: u64, from_end: bool }, + Subslice { from: u64, to: u64 }, //Downcast(Option, VariantIdx), OpaqueCast(T), } +impl ProjectionElem { + pub fn projected_ty( + &self, + base: Ty, + db: &dyn HirDatabase, + closure_field: impl FnOnce(ClosureId, &Substitution, usize) -> Ty, + krate: CrateId, + ) -> Ty { + match self { + ProjectionElem::Deref => match &base.data(Interner).kind { + TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(), + TyKind::Adt(adt, subst) if is_box(db, adt.0) => { + subst.at(Interner, 0).assert_ty_ref(Interner).clone() + } + _ => { + never!("Overloaded deref on type {} is not a projection", base.display(db)); + return TyKind::Error.intern(Interner); + } + }, + ProjectionElem::Field(f) => match &base.data(Interner).kind { + TyKind::Adt(_, subst) => { + db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst) + } + _ => { + never!("Only adt has field"); + return TyKind::Error.intern(Interner); + } + }, + ProjectionElem::TupleOrClosureField(f) => match &base.data(Interner).kind { + TyKind::Tuple(_, subst) => subst + .as_slice(Interner) + .get(*f) + .map(|x| x.assert_ty_ref(Interner)) + .cloned() + .unwrap_or_else(|| { + never!("Out of bound tuple field"); + TyKind::Error.intern(Interner) + }), + TyKind::Closure(id, subst) => closure_field(*id, subst, *f), + _ => { + never!("Only tuple or closure has tuple or closure field"); + return TyKind::Error.intern(Interner); + } + }, + ProjectionElem::ConstantIndex { .. } | ProjectionElem::Index(_) => { + match &base.data(Interner).kind { + TyKind::Array(inner, _) | TyKind::Slice(inner) => inner.clone(), + _ => { + never!("Overloaded index is not a projection"); + return TyKind::Error.intern(Interner); + } + } + } + &ProjectionElem::Subslice { from, to } => match &base.data(Interner).kind { + TyKind::Array(inner, c) => { + let next_c = usize_const( + db, + match try_const_usize(db, c) { + None => None, + Some(x) => x.checked_sub(u128::from(from + to)), + }, + krate, + ); + TyKind::Array(inner.clone(), next_c).intern(Interner) + } + TyKind::Slice(_) => base.clone(), + _ => { + never!("Subslice projection should only happen on slice and array"); + return TyKind::Error.intern(Interner); + } + }, + ProjectionElem::OpaqueCast(_) => { + never!("We don't emit these yet"); + return TyKind::Error.intern(Interner); + } + } + } +} + type PlaceElem = ProjectionElem; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Place { pub local: LocalId, - pub projection: Vec, + pub projection: Box<[PlaceElem]>, +} + +impl Place { + fn is_parent(&self, child: &Place) -> bool { + self.local == child.local && child.projection.starts_with(&self.projection) + } + + fn iterate_over_parents(&self) -> impl Iterator + '_ { + (0..self.projection.len()) + .map(|x| &self.projection[0..x]) + .map(|x| Place { local: self.local, projection: x.to_vec().into() }) + } + + fn project(&self, projection: PlaceElem) -> Place { + Place { + local: self.local, + projection: self.projection.iter().cloned().chain([projection]).collect(), + } + } } impl From for Place { fn from(local: LocalId) -> Self { - Self { local, projection: vec![] } + Self { local, projection: vec![].into() } } } @@ -123,7 +246,7 @@ pub enum AggregateKind { Tuple(Ty), Adt(VariantId, Substitution), Union(UnionId, FieldId), - //Closure(LocalDefId, SubstsRef), + Closure(Ty), //Generator(LocalDefId, SubstsRef, Movability), } @@ -197,7 +320,13 @@ pub fn target_for_value(&self, value: u128) -> BasicBlockId { } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum Terminator { +pub struct Terminator { + span: MirSpan, + kind: TerminatorKind, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TerminatorKind { /// Block has one successor; we continue execution there. Goto { target: BasicBlockId }, @@ -320,7 +449,7 @@ pub enum Terminator { /// These are owned by the callee, which is free to modify them. /// This allows the memory occupied by "by-value" arguments to be /// reused across function calls without duplicating the contents. - args: Vec, + args: Box<[Operand]>, /// Where the returned value will be written destination: Place, /// Where to go after this call returns. If none, the call necessarily diverges. @@ -418,7 +547,7 @@ pub enum Terminator { }, } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum BorrowKind { /// Data must be immutable and is aliasable. Shared, @@ -564,6 +693,20 @@ pub enum BinOp { Offset, } +impl BinOp { + fn run_compare(&self, l: T, r: T) -> bool { + match self { + BinOp::Ge => l >= r, + BinOp::Gt => l > r, + BinOp::Le => l <= r, + BinOp::Lt => l < r, + BinOp::Eq => l == r, + BinOp::Ne => l != r, + x => panic!("`run_compare` called on operator {x:?}"), + } + } +} + impl Display for BinOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { @@ -588,32 +731,32 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { } } -impl From for BinOp { - fn from(value: hir_def::expr::ArithOp) -> Self { +impl From for BinOp { + fn from(value: hir_def::hir::ArithOp) -> Self { match value { - hir_def::expr::ArithOp::Add => BinOp::Add, - hir_def::expr::ArithOp::Mul => BinOp::Mul, - hir_def::expr::ArithOp::Sub => BinOp::Sub, - hir_def::expr::ArithOp::Div => BinOp::Div, - hir_def::expr::ArithOp::Rem => BinOp::Rem, - hir_def::expr::ArithOp::Shl => BinOp::Shl, - hir_def::expr::ArithOp::Shr => BinOp::Shr, - hir_def::expr::ArithOp::BitXor => BinOp::BitXor, - hir_def::expr::ArithOp::BitOr => BinOp::BitOr, - hir_def::expr::ArithOp::BitAnd => BinOp::BitAnd, + hir_def::hir::ArithOp::Add => BinOp::Add, + hir_def::hir::ArithOp::Mul => BinOp::Mul, + hir_def::hir::ArithOp::Sub => BinOp::Sub, + hir_def::hir::ArithOp::Div => BinOp::Div, + hir_def::hir::ArithOp::Rem => BinOp::Rem, + hir_def::hir::ArithOp::Shl => BinOp::Shl, + hir_def::hir::ArithOp::Shr => BinOp::Shr, + hir_def::hir::ArithOp::BitXor => BinOp::BitXor, + hir_def::hir::ArithOp::BitOr => BinOp::BitOr, + hir_def::hir::ArithOp::BitAnd => BinOp::BitAnd, } } } -impl From for BinOp { - fn from(value: hir_def::expr::CmpOp) -> Self { +impl From for BinOp { + fn from(value: hir_def::hir::CmpOp) -> Self { match value { - hir_def::expr::CmpOp::Eq { negated: false } => BinOp::Eq, - hir_def::expr::CmpOp::Eq { negated: true } => BinOp::Ne, - hir_def::expr::CmpOp::Ord { ordering: Ordering::Greater, strict: false } => BinOp::Ge, - hir_def::expr::CmpOp::Ord { ordering: Ordering::Greater, strict: true } => BinOp::Gt, - hir_def::expr::CmpOp::Ord { ordering: Ordering::Less, strict: false } => BinOp::Le, - hir_def::expr::CmpOp::Ord { ordering: Ordering::Less, strict: true } => BinOp::Lt, + hir_def::hir::CmpOp::Eq { negated: false } => BinOp::Eq, + hir_def::hir::CmpOp::Eq { negated: true } => BinOp::Ne, + hir_def::hir::CmpOp::Ord { ordering: Ordering::Greater, strict: false } => BinOp::Ge, + hir_def::hir::CmpOp::Ord { ordering: Ordering::Greater, strict: true } => BinOp::Gt, + hir_def::hir::CmpOp::Ord { ordering: Ordering::Less, strict: false } => BinOp::Le, + hir_def::hir::CmpOp::Ord { ordering: Ordering::Less, strict: true } => BinOp::Lt, } } } @@ -642,7 +785,6 @@ pub enum CastKind { FloatToInt, FloatToFloat, IntToFloat, - PtrToPtr, FnPtrToPtr, } @@ -653,13 +795,8 @@ pub enum Rvalue { /// Creates an array where each element is the value of the operand. /// - /// This is the cause of a bug in the case where the repetition count is zero because the value - /// is not dropped, see [#74836]. - /// /// Corresponds to source code like `[x; 32]`. - /// - /// [#74836]: https://github.com/rust-lang/rust/issues/74836 - //Repeat(Operand, ty::Const), + Repeat(Operand, Const), /// Creates a reference of the indicated kind to the place. /// @@ -768,7 +905,7 @@ pub enum Rvalue { /// /// Disallowed after deaggregation for all aggregate kinds except `Array` and `Generator`. After /// generator lowering, `Generator` aggregate kinds are disallowed too. - Aggregate(AggregateKind, Vec), + Aggregate(AggregateKind, Box<[Operand]>), /// Transmutes a `*mut u8` into shallow-initialized `Box`. /// @@ -777,6 +914,9 @@ pub enum Rvalue { /// affects alias analysis. ShallowInitBox(Operand, Ty), + /// NON STANDARD: allocates memory with the type's layout, and shallow init the box with the resulting pointer. + ShallowInitBoxWithAlloc(Ty), + /// A CopyForDeref is equivalent to a read from a place at the /// codegen level, but is treated specially by drop elaboration. When such a read happens, it /// is guaranteed (via nature of the mir_opt `Derefer` in rustc_mir_transform/src/deref_separator) @@ -816,7 +956,7 @@ pub struct Statement { pub span: MirSpan, } -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct BasicBlock { /// List of statements in this block. pub statements: Vec, @@ -838,19 +978,118 @@ pub struct BasicBlock { pub is_cleanup: bool, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MirBody { pub basic_blocks: Arena, pub locals: Arena, pub start_block: BasicBlockId, pub owner: DefWithBodyId, - pub arg_count: usize, pub binding_locals: ArenaMap, pub param_locals: Vec, + /// This field stores the closures directly owned by this body. It is used + /// in traversing every mir body. + pub closures: Vec, } -fn const_as_usize(c: &Const) -> usize { - try_const_usize(c).unwrap() as usize +impl MirBody { + fn walk_places(&mut self, mut f: impl FnMut(&mut Place)) { + fn for_operand(op: &mut Operand, f: &mut impl FnMut(&mut Place)) { + match op { + Operand::Copy(p) | Operand::Move(p) => { + f(p); + } + Operand::Constant(_) | Operand::Static(_) => (), + } + } + for (_, block) in self.basic_blocks.iter_mut() { + for statement in &mut block.statements { + match &mut statement.kind { + StatementKind::Assign(p, r) => { + f(p); + match r { + Rvalue::ShallowInitBoxWithAlloc(_) => (), + Rvalue::ShallowInitBox(o, _) + | Rvalue::UnaryOp(_, o) + | Rvalue::Cast(_, o, _) + | Rvalue::Repeat(o, _) + | Rvalue::Use(o) => for_operand(o, &mut f), + Rvalue::CopyForDeref(p) + | Rvalue::Discriminant(p) + | Rvalue::Len(p) + | Rvalue::Ref(_, p) => f(p), + Rvalue::CheckedBinaryOp(_, o1, o2) => { + for_operand(o1, &mut f); + for_operand(o2, &mut f); + } + Rvalue::Aggregate(_, ops) => { + for op in ops.iter_mut() { + for_operand(op, &mut f); + } + } + } + } + StatementKind::Deinit(p) => f(p), + StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Nop => (), + } + } + match &mut block.terminator { + Some(x) => match &mut x.kind { + TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, &mut f), + TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::Resume + | TerminatorKind::GeneratorDrop + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable => (), + TerminatorKind::Drop { place, .. } => { + f(place); + } + TerminatorKind::DropAndReplace { place, value, .. } => { + f(place); + for_operand(value, &mut f); + } + TerminatorKind::Call { func, args, destination, .. } => { + for_operand(func, &mut f); + args.iter_mut().for_each(|x| for_operand(x, &mut f)); + f(destination); + } + TerminatorKind::Assert { cond, .. } => { + for_operand(cond, &mut f); + } + TerminatorKind::Yield { value, resume_arg, .. } => { + for_operand(value, &mut f); + f(resume_arg); + } + }, + None => (), + } + } + } + + fn shrink_to_fit(&mut self) { + let MirBody { + basic_blocks, + locals, + start_block: _, + owner: _, + binding_locals, + param_locals, + closures, + } = self; + basic_blocks.shrink_to_fit(); + locals.shrink_to_fit(); + binding_locals.shrink_to_fit(); + param_locals.shrink_to_fit(); + closures.shrink_to_fit(); + for (_, b) in basic_blocks.iter_mut() { + let BasicBlock { statements, terminator: _, is_cleanup: _ } = b; + statements.shrink_to_fit(); + } + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs index c8729af86a9e..a0ea1cc5ef7d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs @@ -3,17 +3,20 @@ // Currently it is an ad-hoc implementation, only useful for mutability analysis. Feel free to remove all of these // if needed for implementing a proper borrow checker. -use std::sync::Arc; +use std::iter; -use hir_def::DefWithBodyId; +use hir_def::{DefWithBodyId, HasModule}; use la_arena::ArenaMap; use stdx::never; +use triomphe::Arc; -use crate::db::HirDatabase; +use crate::{ + db::HirDatabase, mir::Operand, utils::ClosureSubst, ClosureId, Interner, Ty, TyExt, TypeFlags, +}; use super::{ BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, Place, ProjectionElem, - Rvalue, StatementKind, Terminator, + Rvalue, StatementKind, TerminatorKind, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -23,26 +26,167 @@ pub enum MutabilityReason { Not, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MovedOutOfRef { + pub ty: Ty, + pub span: MirSpan, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct BorrowckResult { pub mir_body: Arc, pub mutability_of_locals: ArenaMap, + pub moved_out_of_ref: Vec, +} + +fn all_mir_bodies( + db: &dyn HirDatabase, + def: DefWithBodyId, +) -> Box, MirLowerError>> + '_> { + fn for_closure( + db: &dyn HirDatabase, + c: ClosureId, + ) -> Box, MirLowerError>> + '_> { + match db.mir_body_for_closure(c) { + Ok(body) => { + let closures = body.closures.clone(); + Box::new( + iter::once(Ok(body)) + .chain(closures.into_iter().flat_map(|x| for_closure(db, x))), + ) + } + Err(e) => Box::new(iter::once(Err(e))), + } + } + match db.mir_body(def) { + Ok(body) => { + let closures = body.closures.clone(); + Box::new( + iter::once(Ok(body)).chain(closures.into_iter().flat_map(|x| for_closure(db, x))), + ) + } + Err(e) => Box::new(iter::once(Err(e))), + } } pub fn borrowck_query( db: &dyn HirDatabase, def: DefWithBodyId, -) -> Result, MirLowerError> { +) -> Result, MirLowerError> { let _p = profile::span("borrowck_query"); - let body = db.mir_body(def)?; - let r = BorrowckResult { mutability_of_locals: mutability_of_locals(&body), mir_body: body }; - Ok(Arc::new(r)) + let r = all_mir_bodies(db, def) + .map(|body| { + let body = body?; + Ok(BorrowckResult { + mutability_of_locals: mutability_of_locals(db, &body), + moved_out_of_ref: moved_out_of_ref(db, &body), + mir_body: body, + }) + }) + .collect::, MirLowerError>>()?; + Ok(r.into()) } -fn is_place_direct(lvalue: &Place) -> bool { - !lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref) +fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec { + let mut result = vec![]; + let mut for_operand = |op: &Operand, span: MirSpan| match op { + Operand::Copy(p) | Operand::Move(p) => { + let mut ty: Ty = body.locals[p.local].ty.clone(); + let mut is_dereference_of_ref = false; + for proj in &*p.projection { + if *proj == ProjectionElem::Deref && ty.as_reference().is_some() { + is_dereference_of_ref = true; + } + ty = proj.projected_ty( + ty, + db, + |c, subst, f| { + let (def, _) = db.lookup_intern_closure(c.into()); + let infer = db.infer(def); + let (captures, _) = infer.closure_info(&c); + let parent_subst = ClosureSubst(subst).parent_subst(); + captures + .get(f) + .expect("broken closure field") + .ty + .clone() + .substitute(Interner, parent_subst) + }, + body.owner.module(db.upcast()).krate(), + ); + } + if is_dereference_of_ref + && !ty.clone().is_copy(db, body.owner) + && !ty.data(Interner).flags.intersects(TypeFlags::HAS_ERROR) + { + result.push(MovedOutOfRef { span, ty }); + } + } + Operand::Constant(_) | Operand::Static(_) => (), + }; + for (_, block) in body.basic_blocks.iter() { + for statement in &block.statements { + match &statement.kind { + StatementKind::Assign(_, r) => match r { + Rvalue::ShallowInitBoxWithAlloc(_) => (), + Rvalue::ShallowInitBox(o, _) + | Rvalue::UnaryOp(_, o) + | Rvalue::Cast(_, o, _) + | Rvalue::Repeat(o, _) + | Rvalue::Use(o) => for_operand(o, statement.span), + Rvalue::CopyForDeref(_) + | Rvalue::Discriminant(_) + | Rvalue::Len(_) + | Rvalue::Ref(_, _) => (), + Rvalue::CheckedBinaryOp(_, o1, o2) => { + for_operand(o1, statement.span); + for_operand(o2, statement.span); + } + Rvalue::Aggregate(_, ops) => { + for op in ops.iter() { + for_operand(op, statement.span); + } + } + }, + StatementKind::Deinit(_) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Nop => (), + } + } + match &block.terminator { + Some(terminator) => match &terminator.kind { + TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, terminator.span), + TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::Resume + | TerminatorKind::GeneratorDrop + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Drop { .. } => (), + TerminatorKind::DropAndReplace { value, .. } => { + for_operand(value, terminator.span); + } + TerminatorKind::Call { func, args, .. } => { + for_operand(func, terminator.span); + args.iter().for_each(|x| for_operand(x, terminator.span)); + } + TerminatorKind::Assert { cond, .. } => { + for_operand(cond, terminator.span); + } + TerminatorKind::Yield { value, .. } => { + for_operand(value, terminator.span); + } + }, + None => (), + } + } + result } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ProjectionCase { /// Projection is a local Direct, @@ -52,20 +196,39 @@ enum ProjectionCase { Indirect, } -fn place_case(lvalue: &Place) -> ProjectionCase { +fn place_case(db: &dyn HirDatabase, body: &MirBody, lvalue: &Place) -> ProjectionCase { let mut is_part_of = false; - for proj in lvalue.projection.iter().rev() { + let mut ty = body.locals[lvalue.local].ty.clone(); + for proj in lvalue.projection.iter() { match proj { - ProjectionElem::Deref => return ProjectionCase::Indirect, // It's indirect - ProjectionElem::ConstantIndex { .. } + ProjectionElem::Deref if ty.as_adt().is_none() => return ProjectionCase::Indirect, // It's indirect in case of reference and raw + ProjectionElem::Deref // It's direct in case of `Box` + | ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } | ProjectionElem::Field(_) - | ProjectionElem::TupleField(_) + | ProjectionElem::TupleOrClosureField(_) | ProjectionElem::Index(_) => { is_part_of = true; } ProjectionElem::OpaqueCast(_) => (), } + ty = proj.projected_ty( + ty, + db, + |c, subst, f| { + let (def, _) = db.lookup_intern_closure(c.into()); + let infer = db.infer(def); + let (captures, _) = infer.closure_info(&c); + let parent_subst = ClosureSubst(subst).parent_subst(); + captures + .get(f) + .expect("broken closure field") + .ty + .clone() + .substitute(Interner, parent_subst) + }, + body.owner.module(db.upcast()).krate(), + ); } if is_part_of { ProjectionCase::DirectPart @@ -76,7 +239,7 @@ fn place_case(lvalue: &Place) -> ProjectionCase { /// Returns a map from basic blocks to the set of locals that might be ever initialized before /// the start of the block. Only `StorageDead` can remove something from this map, and we ignore -/// `Uninit` and `drop` and similars after initialization. +/// `Uninit` and `drop` and similar after initialization. fn ever_initialized_map(body: &MirBody) -> ArenaMap> { let mut result: ArenaMap> = body.basic_blocks.iter().map(|x| (x.0, ArenaMap::default())).collect(); @@ -107,26 +270,28 @@ fn dfs( never!("Terminator should be none only in construction"); return; }; - let targets = match terminator { - Terminator::Goto { target } => vec![*target], - Terminator::SwitchInt { targets, .. } => targets.all_targets().to_vec(), - Terminator::Resume - | Terminator::Abort - | Terminator::Return - | Terminator::Unreachable => vec![], - Terminator::Call { target, cleanup, destination, .. } => { + let targets = match &terminator.kind { + TerminatorKind::Goto { target } => vec![*target], + TerminatorKind::SwitchInt { targets, .. } => targets.all_targets().to_vec(), + TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable => vec![], + TerminatorKind::Call { target, cleanup, destination, .. } => { if destination.projection.len() == 0 && destination.local == l { is_ever_initialized = true; } target.into_iter().chain(cleanup.into_iter()).copied().collect() } - Terminator::Drop { .. } - | Terminator::DropAndReplace { .. } - | Terminator::Assert { .. } - | Terminator::Yield { .. } - | Terminator::GeneratorDrop - | Terminator::FalseEdge { .. } - | Terminator::FalseUnwind { .. } => { + TerminatorKind::Drop { target, unwind, place: _ } => { + Some(target).into_iter().chain(unwind.into_iter()).copied().collect() + } + TerminatorKind::DropAndReplace { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::GeneratorDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } => { never!("We don't emit these MIR terminators yet"); vec![] } @@ -151,7 +316,10 @@ fn dfs( result } -fn mutability_of_locals(body: &MirBody) -> ArenaMap { +fn mutability_of_locals( + db: &dyn HirDatabase, + body: &MirBody, +) -> ArenaMap { let mut result: ArenaMap = body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect(); let mut push_mut_span = |local, span| match &mut result[local] { @@ -164,7 +332,7 @@ fn mutability_of_locals(body: &MirBody) -> ArenaMap { for statement in &block.statements { match &statement.kind { StatementKind::Assign(place, value) => { - match place_case(place) { + match place_case(db, body, place) { ProjectionCase::Direct => { if ever_init_map.get(place.local).copied().unwrap_or_default() { push_mut_span(place.local, statement.span); @@ -179,7 +347,7 @@ fn mutability_of_locals(body: &MirBody) -> ArenaMap { ProjectionCase::Indirect => (), } if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value { - if is_place_direct(p) { + if place_case(db, body, p) != ProjectionCase::Indirect { push_mut_span(p.local, statement.span); } } @@ -194,21 +362,21 @@ fn mutability_of_locals(body: &MirBody) -> ArenaMap { never!("Terminator should be none only in construction"); continue; }; - match terminator { - Terminator::Goto { .. } - | Terminator::Resume - | Terminator::Abort - | Terminator::Return - | Terminator::Unreachable - | Terminator::FalseEdge { .. } - | Terminator::FalseUnwind { .. } - | Terminator::GeneratorDrop - | Terminator::SwitchInt { .. } - | Terminator::Drop { .. } - | Terminator::DropAndReplace { .. } - | Terminator::Assert { .. } - | Terminator::Yield { .. } => (), - Terminator::Call { destination, .. } => { + match &terminator.kind { + TerminatorKind::Goto { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::GeneratorDrop + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Drop { .. } + | TerminatorKind::DropAndReplace { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::Yield { .. } => (), + TerminatorKind::Call { destination, .. } => { if destination.projection.len() == 0 { if ever_init_map.get(destination.local).copied().unwrap_or_default() { push_mut_span(destination.local, MirSpan::Unknown); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index c5d843d9ebd8..ce14f6dbad53 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -1,41 +1,134 @@ //! This module provides a MIR interpreter, which is used in const eval. -use std::{borrow::Cow, collections::HashMap, iter}; +use std::{borrow::Cow, collections::HashMap, fmt::Write, iter, ops::Range}; -use base_db::CrateId; -use chalk_ir::{ - fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable}, - DebruijnIndex, TyKind, -}; +use base_db::{CrateId, FileId}; +use chalk_ir::Mutability; +use either::Either; use hir_def::{ builtin_type::BuiltinType, + data::adt::{StructFlags, VariantData}, lang_item::{lang_attr, LangItem}, - layout::{Layout, LayoutError, RustcEnumVariantIdx, TagEncoding, Variants}, - AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, Lookup, VariantId, + layout::{TagEncoding, Variants}, + AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, StaticId, + VariantId, }; +use hir_expand::InFile; use intern::Interned; use la_arena::ArenaMap; +use rustc_hash::{FxHashMap, FxHashSet}; +use stdx::never; +use syntax::{SyntaxNodePtr, TextRange}; +use triomphe::Arc; use crate::{ - consteval::{intern_const_scalar, ConstEvalError}, + consteval::{intern_const_scalar, try_const_usize, ConstEvalError}, db::HirDatabase, - from_placeholder_idx, - infer::{normalize, PointerCast}, - layout::layout_of_ty, + display::{ClosureStyle, HirDisplay}, + infer::PointerCast, + layout::{Layout, LayoutError, RustcEnumVariantIdx}, mapping::from_chalk, - method_resolution::lookup_impl_method, - CallableDefId, Const, ConstScalar, Interner, MemoryMap, Substitution, Ty, TyBuilder, TyExt, + method_resolution::{is_dyn_method, lookup_impl_method}, + name, static_lifetime, + traits::FnTrait, + utils::{detect_variant_from_bytes, ClosureSubst}, + CallableDefId, ClosureId, Const, ConstScalar, FnDefId, GenericArgData, Interner, MemoryMap, + Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, }; use super::{ - const_as_usize, return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError, - Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, UnOp, + return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError, MirSpan, Operand, + Place, ProjectionElem, Rvalue, StatementKind, TerminatorKind, UnOp, }; +mod shim; +#[cfg(test)] +mod tests; + +macro_rules! from_bytes { + ($ty:tt, $value:expr) => { + ($ty::from_le_bytes(match ($value).try_into() { + Ok(x) => x, + Err(_) => return Err(MirEvalError::TypeError(stringify!(mismatched size in constructing $ty))), + })) + }; +} + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirEvalError::NotSupported(format!($x))) + }; +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct VTableMap { + ty_to_id: FxHashMap, + id_to_ty: Vec, +} + +impl VTableMap { + fn id(&mut self, ty: Ty) -> usize { + if let Some(x) = self.ty_to_id.get(&ty) { + return *x; + } + let id = self.id_to_ty.len(); + self.id_to_ty.push(ty.clone()); + self.ty_to_id.insert(ty, id); + id + } + + fn ty(&self, id: usize) -> Result<&Ty> { + self.id_to_ty.get(id).ok_or(MirEvalError::InvalidVTableId(id)) + } + + fn ty_of_bytes(&self, bytes: &[u8]) -> Result<&Ty> { + let id = from_bytes!(usize, bytes); + self.ty(id) + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +struct TlsData { + keys: Vec, +} + +impl TlsData { + fn create_key(&mut self) -> usize { + self.keys.push(0); + self.keys.len() - 1 + } + + fn get_key(&mut self, key: usize) -> Result { + let r = self.keys.get(key).ok_or_else(|| { + MirEvalError::UndefinedBehavior(format!("Getting invalid tls key {key}")) + })?; + Ok(*r) + } + + fn set_key(&mut self, key: usize, value: u128) -> Result<()> { + let r = self.keys.get_mut(key).ok_or_else(|| { + MirEvalError::UndefinedBehavior(format!("Setting invalid tls key {key}")) + })?; + *r = value; + Ok(()) + } +} + pub struct Evaluator<'a> { db: &'a dyn HirDatabase, + trait_env: Arc, stack: Vec, heap: Vec, + /// Stores the global location of the statics. We const evaluate every static first time we need it + /// and see it's missing, then we add it to this to reuse. + static_locations: FxHashMap, + /// We don't really have function pointers, i.e. pointers to some assembly instructions that we can run. Instead, we + /// store the type as an interned id in place of function and vtable pointers, and we recover back the type at the + /// time of use. + vtable_map: VTableMap, + thread_local_storage: TlsData, + stdout: Vec, + stderr: Vec, crate_id: CrateId, // FIXME: This is a workaround, see the comment on `interpret_mir` assert_placeholder_ty_is_unused: bool, @@ -45,19 +138,27 @@ pub struct Evaluator<'a> { stack_depth_limit: usize, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum Address { Stack(usize), Heap(usize), + Invalid(usize), } use Address::*; +#[derive(Debug, Clone, Copy)] struct Interval { addr: Address, size: usize, } +#[derive(Debug, Clone)] +struct IntervalAndTy { + interval: Interval, + ty: Ty, +} + impl Interval { fn new(addr: Address, size: usize) -> Self { Self { addr, size } @@ -66,12 +167,49 @@ fn new(addr: Address, size: usize) -> Self { fn get<'a>(&self, memory: &'a Evaluator<'a>) -> Result<&'a [u8]> { memory.read_memory(self.addr, self.size) } + + fn write_from_bytes(&self, memory: &mut Evaluator<'_>, bytes: &[u8]) -> Result<()> { + memory.write_memory(self.addr, bytes) + } + + fn write_from_interval(&self, memory: &mut Evaluator<'_>, interval: Interval) -> Result<()> { + // FIXME: this could be more efficient + let bytes = &interval.get(memory)?.to_vec(); + memory.write_memory(self.addr, bytes) + } + + fn slice(self, range: Range) -> Interval { + Interval { addr: self.addr.offset(range.start), size: range.len() } + } +} + +impl IntervalAndTy { + fn get<'a>(&self, memory: &'a Evaluator<'a>) -> Result<&'a [u8]> { + memory.read_memory(self.interval.addr, self.interval.size) + } + + fn new( + addr: Address, + ty: Ty, + evaluator: &Evaluator<'_>, + locals: &Locals<'_>, + ) -> Result { + let size = evaluator.size_of_sized(&ty, locals, "type of interval")?; + Ok(IntervalAndTy { interval: Interval { addr, size }, ty }) + } } enum IntervalOrOwned { Owned(Vec), Borrowed(Interval), } + +impl From for IntervalOrOwned { + fn from(it: Interval) -> IntervalOrOwned { + IntervalOrOwned::Borrowed(it) + } +} + impl IntervalOrOwned { pub(crate) fn to_vec(self, memory: &Evaluator<'_>) -> Result> { Ok(match self { @@ -79,15 +217,13 @@ pub(crate) fn to_vec(self, memory: &Evaluator<'_>) -> Result> { IntervalOrOwned::Borrowed(b) => b.get(memory)?.to_vec(), }) } -} -macro_rules! from_bytes { - ($ty:tt, $value:expr) => { - ($ty::from_le_bytes(match ($value).try_into() { - Ok(x) => x, - Err(_) => return Err(MirEvalError::TypeError("mismatched size")), - })) - }; + fn get<'a>(&'a self, memory: &'a Evaluator<'a>) -> Result<&'a [u8]> { + Ok(match self { + IntervalOrOwned::Owned(o) => o, + IntervalOrOwned::Borrowed(b) => b.get(memory)?, + }) + } } impl Address { @@ -97,9 +233,11 @@ fn from_bytes(x: &[u8]) -> Result { fn from_usize(x: usize) -> Self { if x > usize::MAX / 2 { - Stack(usize::MAX - x) + Stack(x - usize::MAX / 2) + } else if x > usize::MAX / 4 { + Heap(x - usize::MAX / 4) } else { - Heap(x) + Invalid(x) } } @@ -109,8 +247,9 @@ fn to_bytes(&self) -> Vec { fn to_usize(&self) -> usize { let as_num = match self { - Stack(x) => usize::MAX - *x, - Heap(x) => *x, + Stack(x) => *x + usize::MAX / 2, + Heap(x) => *x + usize::MAX / 4, + Invalid(x) => *x, }; as_num } @@ -119,6 +258,7 @@ fn map(&self, f: impl FnOnce(usize) -> usize) -> Address { match self { Stack(x) => Stack(f(*x)), Heap(x) => Heap(f(*x)), + Invalid(x) => Invalid(f(*x)), } } @@ -129,28 +269,123 @@ fn offset(&self, offset: usize) -> Address { #[derive(Clone, PartialEq, Eq)] pub enum MirEvalError { - ConstEvalError(Box), + ConstEvalError(String, Box), LayoutError(LayoutError, Ty), /// Means that code had type errors (or mismatched args) and we shouldn't generate mir in first place. TypeError(&'static str), /// Means that code had undefined behavior. We don't try to actively detect UB, but if it was detected /// then use this type of error. - UndefinedBehavior(&'static str), - Panic, + UndefinedBehavior(String), + Panic(String), MirLowerError(FunctionId, MirLowerError), + MirLowerErrorForClosure(ClosureId, MirLowerError), TypeIsUnsized(Ty, &'static str), NotSupported(String), InvalidConst(Const), - InFunction(FunctionId, Box), + InFunction(Either, Box, MirSpan, DefWithBodyId), ExecutionLimitExceeded, StackOverflow, TargetDataLayoutNotAvailable, + InvalidVTableId(usize), + CoerceUnsizedError(Ty), + LangItemNotFound(LangItem), +} + +impl MirEvalError { + pub fn pretty_print( + &self, + f: &mut String, + db: &dyn HirDatabase, + span_formatter: impl Fn(FileId, TextRange) -> String, + ) -> std::result::Result<(), std::fmt::Error> { + writeln!(f, "Mir eval error:")?; + let mut err = self; + while let MirEvalError::InFunction(func, e, span, def) = err { + err = e; + match func { + Either::Left(func) => { + let function_name = db.function_data(*func); + writeln!( + f, + "In function {} ({:?})", + function_name.name.display(db.upcast()), + func + )?; + } + Either::Right(clos) => { + writeln!(f, "In {:?}", clos)?; + } + } + let source_map = db.body_with_source_map(*def).1; + let span: InFile = match span { + MirSpan::ExprId(e) => match source_map.expr_syntax(*e) { + Ok(s) => s.map(|x| x.into()), + Err(_) => continue, + }, + MirSpan::PatId(p) => match source_map.pat_syntax(*p) { + Ok(s) => s.map(|x| match x { + Either::Left(e) => e.into(), + Either::Right(e) => e.into(), + }), + Err(_) => continue, + }, + MirSpan::Unknown => continue, + }; + let file_id = span.file_id.original_file(db.upcast()); + let text_range = span.value.text_range(); + writeln!(f, "{}", span_formatter(file_id, text_range))?; + } + match err { + MirEvalError::InFunction(..) => unreachable!(), + MirEvalError::LayoutError(err, ty) => { + write!( + f, + "Layout for type `{}` is not available due {err:?}", + ty.display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string() + )?; + } + MirEvalError::MirLowerError(func, err) => { + let function_name = db.function_data(*func); + writeln!( + f, + "MIR lowering for function `{}` ({:?}) failed due:", + function_name.name.display(db.upcast()), + func + )?; + err.pretty_print(f, db, span_formatter)?; + } + MirEvalError::ConstEvalError(name, err) => { + MirLowerError::ConstEvalError(name.clone(), err.clone()).pretty_print( + f, + db, + span_formatter, + )?; + } + MirEvalError::TypeError(_) + | MirEvalError::UndefinedBehavior(_) + | MirEvalError::Panic(_) + | MirEvalError::MirLowerErrorForClosure(_, _) + | MirEvalError::TypeIsUnsized(_, _) + | MirEvalError::NotSupported(_) + | MirEvalError::InvalidConst(_) + | MirEvalError::ExecutionLimitExceeded + | MirEvalError::StackOverflow + | MirEvalError::TargetDataLayoutNotAvailable + | MirEvalError::CoerceUnsizedError(_) + | MirEvalError::LangItemNotFound(_) + | MirEvalError::InvalidVTableId(_) => writeln!(f, "{:?}", err)?, + } + Ok(()) + } } impl std::fmt::Debug for MirEvalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::ConstEvalError(arg0) => f.debug_tuple("ConstEvalError").field(arg0).finish(), + Self::ConstEvalError(arg0, arg1) => { + f.debug_tuple("ConstEvalError").field(arg0).field(arg1).finish() + } + Self::LangItemNotFound(arg0) => f.debug_tuple("LangItemNotFound").field(arg0).finish(), Self::LayoutError(arg0, arg1) => { f.debug_tuple("LayoutError").field(arg0).field(arg1).finish() } @@ -158,7 +393,7 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Self::UndefinedBehavior(arg0) => { f.debug_tuple("UndefinedBehavior").field(arg0).finish() } - Self::Panic => write!(f, "Panic"), + Self::Panic(msg) => write!(f, "Panic with message:\n{msg:?}"), Self::TargetDataLayoutNotAvailable => write!(f, "TargetDataLayoutNotAvailable"), Self::TypeIsUnsized(ty, it) => write!(f, "{ty:?} is unsized. {it} should be sized."), Self::ExecutionLimitExceeded => write!(f, "execution limit exceeded"), @@ -166,17 +401,24 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Self::MirLowerError(arg0, arg1) => { f.debug_tuple("MirLowerError").field(arg0).field(arg1).finish() } + Self::MirLowerErrorForClosure(arg0, arg1) => { + f.debug_tuple("MirLowerError").field(arg0).field(arg1).finish() + } + Self::CoerceUnsizedError(arg0) => { + f.debug_tuple("CoerceUnsizedError").field(arg0).finish() + } + Self::InvalidVTableId(arg0) => f.debug_tuple("InvalidVTableId").field(arg0).finish(), Self::NotSupported(arg0) => f.debug_tuple("NotSupported").field(arg0).finish(), Self::InvalidConst(arg0) => { let data = &arg0.data(Interner); f.debug_struct("InvalidConst").field("ty", &data.ty).field("value", &arg0).finish() } - Self::InFunction(func, e) => { + Self::InFunction(func, e, span, _) => { let mut e = &**e; - let mut stack = vec![*func]; - while let Self::InFunction(f, next_e) = e { + let mut stack = vec![(*func, *span)]; + while let Self::InFunction(f, next_e, span, _) = e { e = &next_e; - stack.push(*f); + stack.push((*f, *span)); } f.debug_struct("WithStack").field("error", e).field("stack", &stack).finish() } @@ -184,26 +426,33 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { } } -macro_rules! not_supported { - ($x: expr) => { - return Err(MirEvalError::NotSupported(format!($x))) - }; +type Result = std::result::Result; + +#[derive(Debug, Default)] +struct DropFlags { + need_drop: FxHashSet, } -impl From for MirEvalError { - fn from(value: ConstEvalError) -> Self { - match value { - _ => MirEvalError::ConstEvalError(Box::new(value)), +impl DropFlags { + fn add_place(&mut self, p: Place) { + if p.iterate_over_parents().any(|x| self.need_drop.contains(&x)) { + return; } + self.need_drop.retain(|x| !p.is_parent(x)); + self.need_drop.insert(p); + } + + fn remove_place(&mut self, p: &Place) -> bool { + // FIXME: replace parents with parts + self.need_drop.remove(p) } } -type Result = std::result::Result; - +#[derive(Debug)] struct Locals<'a> { - ptr: &'a ArenaMap, + ptr: &'a ArenaMap, body: &'a MirBody, - subst: &'a Substitution, + drop_flags: DropFlags, } pub fn interpret_mir( @@ -215,38 +464,65 @@ pub fn interpret_mir( // a zero size, hoping that they are all outside of our current body. Even without a fix for #7434, we can // (and probably should) do better here, for example by excluding bindings outside of the target expression. assert_placeholder_ty_is_unused: bool, -) -> Result { +) -> (Result, String, String) { let ty = body.locals[return_slot()].ty.clone(); - let mut evaluator = - Evaluator::new(db, body.owner.module(db.upcast()).krate(), assert_placeholder_ty_is_unused); - let bytes = evaluator.interpret_mir_with_no_arg(&body)?; - let memory_map = evaluator.create_memory_map( - &bytes, - &ty, - &Locals { ptr: &ArenaMap::new(), body: &body, subst: &Substitution::empty(Interner) }, - )?; - return Ok(intern_const_scalar(ConstScalar::Bytes(bytes, memory_map), ty)); + let mut evaluator = Evaluator::new(db, body, assert_placeholder_ty_is_unused); + let x: Result = (|| { + let bytes = evaluator.interpret_mir(&body, None.into_iter())?; + let mut memory_map = evaluator.create_memory_map( + &bytes, + &ty, + &Locals { ptr: &ArenaMap::new(), body: &body, drop_flags: DropFlags::default() }, + )?; + memory_map.vtable = evaluator.vtable_map.clone(); + return Ok(intern_const_scalar(ConstScalar::Bytes(bytes, memory_map), ty)); + })(); + ( + x, + String::from_utf8_lossy(&evaluator.stdout).into_owned(), + String::from_utf8_lossy(&evaluator.stderr).into_owned(), + ) } impl Evaluator<'_> { pub fn new<'a>( db: &'a dyn HirDatabase, - crate_id: CrateId, + body: &MirBody, assert_placeholder_ty_is_unused: bool, ) -> Evaluator<'a> { + let crate_id = body.owner.module(db.upcast()).krate(); + let trait_env = db.trait_environment_for_body(body.owner); Evaluator { stack: vec![0], heap: vec![0], + vtable_map: VTableMap::default(), + thread_local_storage: TlsData::default(), + static_locations: HashMap::default(), db, + trait_env, crate_id, + stdout: vec![], + stderr: vec![], assert_placeholder_ty_is_unused, stack_depth_limit: 100, - execution_limit: 100_000, + execution_limit: 1000_000, } } fn place_addr(&self, p: &Place, locals: &Locals<'_>) -> Result
{ - Ok(self.place_addr_and_ty(p, locals)?.0) + Ok(self.place_addr_and_ty_and_metadata(p, locals)?.0) + } + + fn place_interval(&self, p: &Place, locals: &Locals<'_>) -> Result { + let place_addr_and_ty = self.place_addr_and_ty_and_metadata(p, locals)?; + Ok(Interval { + addr: place_addr_and_ty.0, + size: self.size_of_sized( + &place_addr_and_ty.1, + locals, + "Type of place that we need its interval", + )?, + }) } fn ptr_size(&self) -> usize { @@ -256,125 +532,170 @@ fn ptr_size(&self) -> usize { } } - fn place_addr_and_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<(Address, Ty)> { - let mut addr = locals.ptr[p.local]; - let mut ty: Ty = - self.ty_filler(&locals.body.locals[p.local].ty, locals.subst, locals.body.owner)?; - for proj in &p.projection { + fn place_addr_and_ty_and_metadata<'a>( + &'a self, + p: &Place, + locals: &'a Locals<'a>, + ) -> Result<(Address, Ty, Option)> { + let mut addr = locals.ptr[p.local].addr; + let mut ty: Ty = locals.body.locals[p.local].ty.clone(); + let mut metadata: Option = None; // locals are always sized + for proj in &*p.projection { + let prev_ty = ty.clone(); + ty = proj.projected_ty( + ty, + self.db, + |c, subst, f| { + let (def, _) = self.db.lookup_intern_closure(c.into()); + let infer = self.db.infer(def); + let (captures, _) = infer.closure_info(&c); + let parent_subst = ClosureSubst(subst).parent_subst(); + captures + .get(f) + .expect("broken closure field") + .ty + .clone() + .substitute(Interner, parent_subst) + }, + self.crate_id, + ); match proj { ProjectionElem::Deref => { - ty = match &ty.data(Interner).kind { - TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(), - _ => { - return Err(MirEvalError::TypeError( - "Overloaded deref in MIR is disallowed", - )) - } + metadata = if self.size_align_of(&ty, locals)?.is_none() { + Some( + Interval { addr: addr.offset(self.ptr_size()), size: self.ptr_size() } + .into(), + ) + } else { + None }; let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?); addr = Address::from_usize(x); } ProjectionElem::Index(op) => { - let offset = - from_bytes!(usize, self.read_memory(locals.ptr[*op], self.ptr_size())?); - match &ty.data(Interner).kind { - TyKind::Ref(_, _, inner) => match &inner.data(Interner).kind { - TyKind::Slice(inner) => { - ty = inner.clone(); - let ty_size = self.size_of_sized( - &ty, - locals, - "slice inner type should be sized", - )?; - let value = self.read_memory(addr, self.ptr_size() * 2)?; - addr = Address::from_bytes(&value[0..8])?.offset(ty_size * offset); - } - x => not_supported!("MIR index for ref type {x:?}"), - }, - TyKind::Array(inner, _) | TyKind::Slice(inner) => { - ty = inner.clone(); - let ty_size = self.size_of_sized( - &ty, - locals, - "array inner type should be sized", - )?; - addr = addr.offset(ty_size * offset); - } - x => not_supported!("MIR index for type {x:?}"), - } + let offset = from_bytes!( + usize, + self.read_memory(locals.ptr[*op].addr, self.ptr_size())? + ); + metadata = None; // Result of index is always sized + let ty_size = + self.size_of_sized(&ty, locals, "array inner type should be sized")?; + addr = addr.offset(ty_size * offset); } - &ProjectionElem::TupleField(f) => match &ty.data(Interner).kind { - TyKind::Tuple(_, subst) => { - let layout = self.layout(&ty)?; - ty = subst - .as_slice(Interner) - .get(f) - .ok_or(MirEvalError::TypeError("not enough tuple fields"))? - .assert_ty_ref(Interner) - .clone(); - let offset = layout.fields.offset(f).bytes_usize(); - addr = addr.offset(offset); - } - _ => return Err(MirEvalError::TypeError("Only tuple has tuple fields")), - }, - ProjectionElem::Field(f) => match &ty.data(Interner).kind { - TyKind::Adt(adt, subst) => { - let layout = self.layout_adt(adt.0, subst.clone())?; - let variant_layout = match &layout.variants { - Variants::Single { .. } => &layout, - Variants::Multiple { variants, .. } => { - &variants[match f.parent { - hir_def::VariantId::EnumVariantId(x) => { - RustcEnumVariantIdx(x.local_id) - } - _ => { - return Err(MirEvalError::TypeError( - "Multivariant layout only happens for enums", - )) - } - }] - } + &ProjectionElem::ConstantIndex { from_end, offset } => { + let offset = if from_end { + let len = match prev_ty.kind(Interner) { + TyKind::Array(_, c) => match try_const_usize(self.db, c) { + Some(x) => x as u64, + None => { + not_supported!("indexing array with unknown const from end") + } + }, + TyKind::Slice(_) => match metadata { + Some(x) => from_bytes!(u64, x.get(self)?), + None => not_supported!("slice place without metadata"), + }, + _ => not_supported!("bad type for const index"), }; - ty = self.db.field_types(f.parent)[f.local_id] - .clone() - .substitute(Interner, subst); - let offset = variant_layout - .fields - .offset(u32::from(f.local_id.into_raw()) as usize) - .bytes_usize(); - addr = addr.offset(offset); - } - _ => return Err(MirEvalError::TypeError("Only adt has fields")), - }, - ProjectionElem::ConstantIndex { .. } => { - not_supported!("constant index") + (len - offset - 1) as usize + } else { + offset as usize + }; + metadata = None; // Result of index is always sized + let ty_size = + self.size_of_sized(&ty, locals, "array inner type should be sized")?; + addr = addr.offset(ty_size * offset); + } + &ProjectionElem::Subslice { from, to } => { + let inner_ty = match &ty.data(Interner).kind { + TyKind::Array(inner, _) | TyKind::Slice(inner) => inner.clone(), + _ => TyKind::Error.intern(Interner), + }; + metadata = match metadata { + Some(x) => { + let prev_len = from_bytes!(u64, x.get(self)?); + Some(IntervalOrOwned::Owned( + (prev_len - from - to).to_le_bytes().to_vec(), + )) + } + None => None, + }; + let ty_size = + self.size_of_sized(&inner_ty, locals, "array inner type should be sized")?; + addr = addr.offset(ty_size * (from as usize)); + } + &ProjectionElem::TupleOrClosureField(f) => { + let layout = self.layout(&prev_ty)?; + let offset = layout.fields.offset(f).bytes_usize(); + addr = addr.offset(offset); + metadata = None; // tuple field is always sized + } + ProjectionElem::Field(f) => { + let layout = self.layout(&prev_ty)?; + let variant_layout = match &layout.variants { + Variants::Single { .. } => &layout, + Variants::Multiple { variants, .. } => { + &variants[match f.parent { + hir_def::VariantId::EnumVariantId(x) => { + RustcEnumVariantIdx(x.local_id) + } + _ => { + return Err(MirEvalError::TypeError( + "Multivariant layout only happens for enums", + )) + } + }] + } + }; + let offset = variant_layout + .fields + .offset(u32::from(f.local_id.into_raw()) as usize) + .bytes_usize(); + addr = addr.offset(offset); + // FIXME: support structs with unsized fields + metadata = None; } - ProjectionElem::Subslice { .. } => not_supported!("subslice"), ProjectionElem::OpaqueCast(_) => not_supported!("opaque cast"), } } - Ok((addr, ty)) + Ok((addr, ty, metadata)) } - fn layout(&self, ty: &Ty) -> Result { - layout_of_ty(self.db, ty, self.crate_id) + fn layout(&self, ty: &Ty) -> Result> { + self.db + .layout_of_ty(ty.clone(), self.crate_id) .map_err(|e| MirEvalError::LayoutError(e, ty.clone())) } - fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result { - self.db.layout_of_adt(adt, subst.clone()).map_err(|e| { + fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result> { + self.db.layout_of_adt(adt, subst.clone(), self.crate_id).map_err(|e| { MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner)) }) } fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result { - Ok(self.place_addr_and_ty(p, locals)?.1) + Ok(self.place_addr_and_ty_and_metadata(p, locals)?.1) } - fn operand_ty<'a>(&'a self, o: &'a Operand, locals: &'a Locals<'a>) -> Result { + fn operand_ty(&self, o: &Operand, locals: &Locals<'_>) -> Result { Ok(match o { Operand::Copy(p) | Operand::Move(p) => self.place_ty(p, locals)?, Operand::Constant(c) => c.data(Interner).ty.clone(), + &Operand::Static(s) => { + let ty = self.db.infer(s.into())[self.db.body(s.into()).body_expr].clone(); + TyKind::Ref(Mutability::Not, static_lifetime(), ty).intern(Interner) + } + }) + } + + fn operand_ty_and_eval( + &mut self, + o: &Operand, + locals: &mut Locals<'_>, + ) -> Result { + Ok(IntervalAndTy { + interval: self.eval_operand(o, locals)?, + ty: self.operand_ty(o, locals)?, }) } @@ -382,7 +703,6 @@ fn interpret_mir( &mut self, body: &MirBody, args: impl Iterator>, - subst: Substitution, ) -> Result> { if let Some(x) = self.stack_depth_limit.checked_sub(1) { self.stack_depth_limit = x; @@ -390,7 +710,8 @@ fn interpret_mir( return Err(MirEvalError::StackOverflow); } let mut current_block_idx = body.start_block; - let mut locals = Locals { ptr: &ArenaMap::new(), body: &body, subst: &subst }; + let mut locals = + Locals { ptr: &ArenaMap::new(), body: &body, drop_flags: DropFlags::default() }; let (locals_ptr, stack_size) = { let mut stack_ptr = self.stack.len(); let addr = body @@ -401,7 +722,7 @@ fn interpret_mir( self.size_of_sized(&x.ty, &locals, "no unsized local in extending stack")?; let my_ptr = stack_ptr; stack_ptr += size; - Ok((id, Stack(my_ptr))) + Ok((id, Interval { addr: Stack(my_ptr), size })) }) .collect::>>()?; let stack_size = stack_ptr - self.stack.len(); @@ -409,9 +730,10 @@ fn interpret_mir( }; locals.ptr = &locals_ptr; self.stack.extend(iter::repeat(0).take(stack_size)); - let mut remain_args = body.arg_count; - for ((_, addr), value) in locals_ptr.iter().skip(1).zip(args) { - self.write_memory(*addr, &value)?; + let mut remain_args = body.param_locals.len(); + for ((l, interval), value) in locals_ptr.iter().skip(1).zip(args) { + locals.drop_flags.add_place(l.into()); + interval.write_from_bytes(self, &value)?; if remain_args == 0 { return Err(MirEvalError::TypeError("more arguments provided")); } @@ -431,8 +753,9 @@ fn interpret_mir( match &statement.kind { StatementKind::Assign(l, r) => { let addr = self.place_addr(l, &locals)?; - let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?; + let result = self.eval_rvalue(r, &mut locals)?.to_vec(&self)?; self.write_memory(addr, &result)?; + locals.drop_flags.add_place(l.clone()); } StatementKind::Deinit(_) => not_supported!("de-init statement"), StatementKind::StorageLive(_) @@ -443,11 +766,11 @@ fn interpret_mir( let Some(terminator) = current_block.terminator.as_ref() else { not_supported!("block without terminator"); }; - match terminator { - Terminator::Goto { target } => { + match &terminator.kind { + TerminatorKind::Goto { target } => { current_block_idx = *target; } - Terminator::Call { + TerminatorKind::Call { func, args, destination, @@ -455,155 +778,84 @@ fn interpret_mir( cleanup: _, from_hir_call: _, } => { + let destination_interval = self.place_interval(destination, &locals)?; let fn_ty = self.operand_ty(func, &locals)?; + let args = args + .iter() + .map(|x| self.operand_ty_and_eval(x, &mut locals)) + .collect::>>()?; match &fn_ty.data(Interner).kind { - TyKind::FnDef(def, generic_args) => { - let def: CallableDefId = from_chalk(self.db, *def); - let generic_args = self.subst_filler(generic_args, &locals); - match def { - CallableDefId::FunctionId(def) => { - let arg_bytes = args - .iter() - .map(|x| { - Ok(self - .eval_operand(x, &locals)? - .get(&self)? - .to_owned()) - }) - .collect::>>()? - .into_iter(); - let function_data = self.db.function_data(def); - let is_intrinsic = match &function_data.abi { - Some(abi) => *abi == Interned::new_str("rust-intrinsic"), - None => match def.lookup(self.db.upcast()).container { - hir_def::ItemContainerId::ExternBlockId(block) => { - let id = block.lookup(self.db.upcast()).id; - id.item_tree(self.db.upcast())[id.value] - .abi - .as_deref() - == Some("rust-intrinsic") - } - _ => false, - }, - }; - let result = if is_intrinsic { - self.exec_intrinsic( - function_data - .name - .as_text() - .unwrap_or_default() - .as_str(), - arg_bytes, - generic_args, - &locals, - )? - } else if let Some(x) = self.detect_lang_function(def) { - self.exec_lang_item(x, arg_bytes)? - } else { - let trait_env = { - let Some(d) = body.owner.as_generic_def_id() else { - not_supported!("trait resolving in non generic def id"); - }; - self.db.trait_environment(d) - }; - let (imp, generic_args) = lookup_impl_method( - self.db, - trait_env, - def, - generic_args.clone(), - ); - let generic_args = - self.subst_filler(&generic_args, &locals); - let def = imp.into(); - let mir_body = self - .db - .mir_body(def) - .map_err(|e| MirEvalError::MirLowerError(imp, e))?; - self.interpret_mir(&mir_body, arg_bytes, generic_args) - .map_err(|e| { - MirEvalError::InFunction(imp, Box::new(e)) - })? - }; - let dest_addr = self.place_addr(destination, &locals)?; - self.write_memory(dest_addr, &result)?; - } - CallableDefId::StructId(id) => { - let (size, variant_layout, tag) = self.layout_of_variant( - id.into(), - generic_args.clone(), - &locals, - )?; - let result = self.make_by_layout( - size, - &variant_layout, - tag, - args, - &locals, - )?; - let dest_addr = self.place_addr(destination, &locals)?; - self.write_memory(dest_addr, &result)?; - } - CallableDefId::EnumVariantId(id) => { - let (size, variant_layout, tag) = self.layout_of_variant( - id.into(), - generic_args.clone(), - &locals, - )?; - let result = self.make_by_layout( - size, - &variant_layout, - tag, - args, - &locals, - )?; - let dest_addr = self.place_addr(destination, &locals)?; - self.write_memory(dest_addr, &result)?; - } - } - current_block_idx = - target.expect("broken mir, function without target"); + TyKind::Function(_) => { + let bytes = self.eval_operand(func, &mut locals)?; + self.exec_fn_pointer( + bytes, + destination_interval, + &args, + &locals, + terminator.span, + )?; } - _ => not_supported!("unknown function type"), + TyKind::FnDef(def, generic_args) => { + self.exec_fn_def( + *def, + generic_args, + destination_interval, + &args, + &locals, + terminator.span, + )?; + } + x => not_supported!("unknown function type {x:?}"), } + locals.drop_flags.add_place(destination.clone()); + current_block_idx = target.expect("broken mir, function without target"); } - Terminator::SwitchInt { discr, targets } => { + TerminatorKind::SwitchInt { discr, targets } => { let val = u128::from_le_bytes(pad16( - self.eval_operand(discr, &locals)?.get(&self)?, + self.eval_operand(discr, &mut locals)?.get(&self)?, false, )); current_block_idx = targets.target_for_value(val); } - Terminator::Return => { - let ty = body.locals[return_slot()].ty.clone(); + TerminatorKind::Return => { self.stack_depth_limit += 1; - return Ok(self - .read_memory( - locals.ptr[return_slot()], - self.size_of_sized(&ty, &locals, "return type")?, - )? - .to_owned()); + return Ok(locals.ptr[return_slot()].get(self)?.to_vec()); } - Terminator::Unreachable => { - return Err(MirEvalError::UndefinedBehavior("unreachable executed")) + TerminatorKind::Unreachable => { + return Err(MirEvalError::UndefinedBehavior("unreachable executed".to_owned())); + } + TerminatorKind::Drop { place, target, unwind: _ } => { + self.drop_place(place, &mut locals, terminator.span)?; + current_block_idx = *target; } _ => not_supported!("unknown terminator"), } } } - fn eval_rvalue<'a>( - &'a mut self, - r: &'a Rvalue, - locals: &'a Locals<'a>, - ) -> Result { + fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals<'_>) -> Result { use IntervalOrOwned::*; Ok(match r { Rvalue::Use(x) => Borrowed(self.eval_operand(x, locals)?), Rvalue::Ref(_, p) => { - let addr = self.place_addr(p, locals)?; - Owned(addr.to_bytes()) + let (addr, _, metadata) = self.place_addr_and_ty_and_metadata(p, locals)?; + let mut r = addr.to_bytes(); + if let Some(metadata) = metadata { + r.extend(metadata.get(self)?); + } + Owned(r) + } + Rvalue::Len(p) => { + let (_, _, metadata) = self.place_addr_and_ty_and_metadata(p, locals)?; + match metadata { + Some(m) => m, + None => { + return Err(MirEvalError::TypeError( + "type without metadata is used for Rvalue::Len", + )); + } + } } - Rvalue::Len(_) => not_supported!("rvalue len"), Rvalue::UnaryOp(op, val) => { let mut c = self.eval_operand(val, locals)?.get(&self)?; let mut ty = self.operand_ty(val, locals)?; @@ -612,27 +864,40 @@ fn eval_rvalue<'a>( let size = self.size_of_sized(&ty, locals, "operand of unary op")?; c = self.read_memory(Address::from_bytes(c)?, size)?; } - let mut c = c.to_vec(); - if ty.as_builtin() == Some(BuiltinType::Bool) { - c[0] = 1 - c[0]; + if let TyKind::Scalar(chalk_ir::Scalar::Float(f)) = ty.kind(Interner) { + match f { + chalk_ir::FloatTy::F32 => { + let c = -from_bytes!(f32, c); + Owned(c.to_le_bytes().into()) + } + chalk_ir::FloatTy::F64 => { + let c = -from_bytes!(f64, c); + Owned(c.to_le_bytes().into()) + } + } } else { - match op { - UnOp::Not => c.iter_mut().for_each(|x| *x = !*x), - UnOp::Neg => { - c.iter_mut().for_each(|x| *x = !*x); - for k in c.iter_mut() { - let o; - (*k, o) = k.overflowing_add(1); - if !o { - break; + let mut c = c.to_vec(); + if ty.as_builtin() == Some(BuiltinType::Bool) { + c[0] = 1 - c[0]; + } else { + match op { + UnOp::Not => c.iter_mut().for_each(|x| *x = !*x), + UnOp::Neg => { + c.iter_mut().for_each(|x| *x = !*x); + for k in c.iter_mut() { + let o; + (*k, o) = k.overflowing_add(1); + if !o { + break; + } } } } } + Owned(c) } - Owned(c) } - Rvalue::CheckedBinaryOp(op, lhs, rhs) => { + Rvalue::CheckedBinaryOp(op, lhs, rhs) => 'binary_op: { let lc = self.eval_operand(lhs, locals)?; let rc = self.eval_operand(rhs, locals)?; let mut lc = lc.get(&self)?; @@ -640,79 +905,170 @@ fn eval_rvalue<'a>( let mut ty = self.operand_ty(lhs, locals)?; while let TyKind::Ref(_, _, z) = ty.kind(Interner) { ty = z.clone(); - let size = self.size_of_sized(&ty, locals, "operand of binary op")?; + let size = if ty.kind(Interner) == &TyKind::Str { + if *op != BinOp::Eq { + never!("Only eq is builtin for `str`"); + } + let ls = from_bytes!(usize, &lc[self.ptr_size()..self.ptr_size() * 2]); + let rs = from_bytes!(usize, &rc[self.ptr_size()..self.ptr_size() * 2]); + if ls != rs { + break 'binary_op Owned(vec![0]); + } + lc = &lc[..self.ptr_size()]; + rc = &rc[..self.ptr_size()]; + ls + } else { + self.size_of_sized(&ty, locals, "operand of binary op")? + }; lc = self.read_memory(Address::from_bytes(lc)?, size)?; rc = self.read_memory(Address::from_bytes(rc)?, size)?; } - let is_signed = matches!(ty.as_builtin(), Some(BuiltinType::Int(_))); - let l128 = i128::from_le_bytes(pad16(lc, is_signed)); - let r128 = i128::from_le_bytes(pad16(rc, is_signed)); - match op { - BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt | BinOp::Eq | BinOp::Ne => { - let r = match op { - BinOp::Ge => l128 >= r128, - BinOp::Gt => l128 > r128, - BinOp::Le => l128 <= r128, - BinOp::Lt => l128 < r128, - BinOp::Eq => l128 == r128, - BinOp::Ne => l128 != r128, - _ => unreachable!(), - }; - let r = r as u8; - Owned(vec![r]) + if let TyKind::Scalar(chalk_ir::Scalar::Float(f)) = ty.kind(Interner) { + match f { + chalk_ir::FloatTy::F32 => { + let l = from_bytes!(f32, lc); + let r = from_bytes!(f32, rc); + match op { + BinOp::Ge + | BinOp::Gt + | BinOp::Le + | BinOp::Lt + | BinOp::Eq + | BinOp::Ne => { + let r = op.run_compare(l, r) as u8; + Owned(vec![r]) + } + BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => { + let r = match op { + BinOp::Add => l + r, + BinOp::Sub => l - r, + BinOp::Mul => l * r, + BinOp::Div => l / r, + _ => unreachable!(), + }; + Owned(r.to_le_bytes().into()) + } + x => not_supported!( + "invalid binop {x:?} on floating point operators" + ), + } + } + chalk_ir::FloatTy::F64 => { + let l = from_bytes!(f64, lc); + let r = from_bytes!(f64, rc); + match op { + BinOp::Ge + | BinOp::Gt + | BinOp::Le + | BinOp::Lt + | BinOp::Eq + | BinOp::Ne => { + let r = op.run_compare(l, r) as u8; + Owned(vec![r]) + } + BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => { + let r = match op { + BinOp::Add => l + r, + BinOp::Sub => l - r, + BinOp::Mul => l * r, + BinOp::Div => l / r, + _ => unreachable!(), + }; + Owned(r.to_le_bytes().into()) + } + x => not_supported!( + "invalid binop {x:?} on floating point operators" + ), + } + } } - BinOp::BitAnd - | BinOp::BitOr - | BinOp::BitXor - | BinOp::Add - | BinOp::Mul - | BinOp::Div - | BinOp::Rem - | BinOp::Sub => { - let r = match op { - BinOp::Add => l128.overflowing_add(r128).0, - BinOp::Mul => l128.overflowing_mul(r128).0, - BinOp::Div => l128.checked_div(r128).ok_or(MirEvalError::Panic)?, - BinOp::Rem => l128.checked_rem(r128).ok_or(MirEvalError::Panic)?, - BinOp::Sub => l128.overflowing_sub(r128).0, - BinOp::BitAnd => l128 & r128, - BinOp::BitOr => l128 | r128, - BinOp::BitXor => l128 ^ r128, - _ => unreachable!(), - }; + } else { + let is_signed = matches!(ty.as_builtin(), Some(BuiltinType::Int(_))); + let l128 = i128::from_le_bytes(pad16(lc, is_signed)); + let r128 = i128::from_le_bytes(pad16(rc, is_signed)); + let check_overflow = |r: i128| { + // FIXME: this is not very correct, and only catches the basic cases. let r = r.to_le_bytes(); for &k in &r[lc.len()..] { if k != 0 && (k != 255 || !is_signed) { - return Err(MirEvalError::Panic); + return Err(MirEvalError::Panic(format!("Overflow in {op:?}"))); } } - Owned(r[0..lc.len()].into()) + Ok(Owned(r[0..lc.len()].into())) + }; + match op { + BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt | BinOp::Eq | BinOp::Ne => { + let r = op.run_compare(l128, r128) as u8; + Owned(vec![r]) + } + BinOp::BitAnd + | BinOp::BitOr + | BinOp::BitXor + | BinOp::Add + | BinOp::Mul + | BinOp::Div + | BinOp::Rem + | BinOp::Sub => { + let r = match op { + BinOp::Add => l128.overflowing_add(r128).0, + BinOp::Mul => l128.overflowing_mul(r128).0, + BinOp::Div => l128.checked_div(r128).ok_or_else(|| { + MirEvalError::Panic(format!("Overflow in {op:?}")) + })?, + BinOp::Rem => l128.checked_rem(r128).ok_or_else(|| { + MirEvalError::Panic(format!("Overflow in {op:?}")) + })?, + BinOp::Sub => l128.overflowing_sub(r128).0, + BinOp::BitAnd => l128 & r128, + BinOp::BitOr => l128 | r128, + BinOp::BitXor => l128 ^ r128, + _ => unreachable!(), + }; + check_overflow(r)? + } + BinOp::Shl | BinOp::Shr => { + let r = 'b: { + if let Ok(shift_amount) = u32::try_from(r128) { + let r = match op { + BinOp::Shl => l128.checked_shl(shift_amount), + BinOp::Shr => l128.checked_shr(shift_amount), + _ => unreachable!(), + }; + if let Some(r) = r { + break 'b r; + } + }; + return Err(MirEvalError::Panic(format!("Overflow in {op:?}"))); + }; + check_overflow(r)? + } + BinOp::Offset => not_supported!("offset binop"), } - BinOp::Shl | BinOp::Shr => { - let shift_amout = if r128 < 0 { - return Err(MirEvalError::Panic); - } else if r128 > 128 { - return Err(MirEvalError::Panic); - } else { - r128 as u8 - }; - let r = match op { - BinOp::Shl => l128 << shift_amout, - BinOp::Shr => l128 >> shift_amout, - _ => unreachable!(), - }; - Owned(r.to_le_bytes()[0..lc.len()].into()) - } - BinOp::Offset => not_supported!("offset binop"), } } Rvalue::Discriminant(p) => { let ty = self.place_ty(p, locals)?; let bytes = self.eval_place(p, locals)?.get(&self)?; let layout = self.layout(&ty)?; - match layout.variants { - Variants::Single { .. } => Owned(0u128.to_le_bytes().to_vec()), - Variants::Multiple { tag, tag_encoding, .. } => { + let enum_id = 'b: { + match ty.kind(Interner) { + TyKind::Adt(e, _) => match e.0 { + AdtId::EnumId(e) => break 'b e, + _ => (), + }, + _ => (), + } + return Ok(Owned(0u128.to_le_bytes().to_vec())); + }; + match &layout.variants { + Variants::Single { index } => { + let r = self.const_eval_discriminant(EnumVariantId { + parent: enum_id, + local_id: index.0, + })?; + Owned(r.to_le_bytes().to_vec()) + } + Variants::Multiple { tag, tag_encoding, variants, .. } => { let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else { not_supported!("missing target data layout"); }; @@ -725,120 +1081,142 @@ fn eval_rvalue<'a>( } TagEncoding::Niche { untagged_variant, niche_start, .. } => { let tag = &bytes[offset..offset + size]; - let candidate_discriminant = i128::from_le_bytes(pad16(tag, false)) - .wrapping_sub(niche_start as i128); - let enum_id = match ty.kind(Interner) { - TyKind::Adt(e, _) => match e.0 { - AdtId::EnumId(e) => e, - _ => not_supported!("Non enum with multi variant layout"), - }, - _ => not_supported!("Non adt with multi variant layout"), - }; - let enum_data = self.db.enum_data(enum_id); - let result = 'b: { - for (local_id, _) in enum_data.variants.iter() { - if candidate_discriminant - == self.db.const_eval_discriminant(EnumVariantId { - parent: enum_id, - local_id, - })? - { - break 'b candidate_discriminant; - } - } - self.db.const_eval_discriminant(EnumVariantId { - parent: enum_id, - local_id: untagged_variant.0, - })? - }; + let candidate_tag = i128::from_le_bytes(pad16(tag, false)) + .wrapping_sub(*niche_start as i128) + as usize; + let variant = variants + .iter_enumerated() + .map(|(x, _)| x) + .filter(|x| x != untagged_variant) + .nth(candidate_tag) + .unwrap_or(*untagged_variant) + .0; + let result = self.const_eval_discriminant(EnumVariantId { + parent: enum_id, + local_id: variant, + })?; Owned(result.to_le_bytes().to_vec()) } } } } } + Rvalue::Repeat(x, len) => { + let len = match try_const_usize(self.db, &len) { + Some(x) => x as usize, + None => not_supported!("non evaluatable array len in repeat Rvalue"), + }; + let val = self.eval_operand(x, locals)?.get(self)?; + let size = len * val.len(); + Owned(val.iter().copied().cycle().take(size).collect()) + } Rvalue::ShallowInitBox(_, _) => not_supported!("shallow init box"), + Rvalue::ShallowInitBoxWithAlloc(ty) => { + let Some((size, align)) = self.size_align_of(ty, locals)? else { + not_supported!("unsized box initialization"); + }; + let addr = self.heap_allocate(size, align); + Owned(addr.to_bytes()) + } Rvalue::CopyForDeref(_) => not_supported!("copy for deref"), - Rvalue::Aggregate(kind, values) => match kind { - AggregateKind::Array(_) => { - let mut r = vec![]; - for x in values { - let value = self.eval_operand(x, locals)?.get(&self)?; - r.extend(value); + Rvalue::Aggregate(kind, values) => { + let values = values + .iter() + .map(|x| self.eval_operand(x, locals)) + .collect::>>()?; + match kind { + AggregateKind::Array(_) => { + let mut r = vec![]; + for x in values { + let value = x.get(&self)?; + r.extend(value); + } + Owned(r) + } + AggregateKind::Tuple(ty) => { + let layout = self.layout(&ty)?; + Owned(self.make_by_layout( + layout.size.bytes_usize(), + &layout, + None, + values.iter().map(|&x| x.into()), + )?) + } + AggregateKind::Union(x, f) => { + let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?; + let offset = layout + .fields + .offset(u32::from(f.local_id.into_raw()) as usize) + .bytes_usize(); + let op = values[0].get(&self)?; + let mut result = vec![0; layout.size.bytes_usize()]; + result[offset..offset + op.len()].copy_from_slice(op); + Owned(result) + } + AggregateKind::Adt(x, subst) => { + let (size, variant_layout, tag) = + self.layout_of_variant(*x, subst.clone(), locals)?; + Owned(self.make_by_layout( + size, + &variant_layout, + tag, + values.iter().map(|&x| x.into()), + )?) + } + AggregateKind::Closure(ty) => { + let layout = self.layout(&ty)?; + Owned(self.make_by_layout( + layout.size.bytes_usize(), + &layout, + None, + values.iter().map(|&x| x.into()), + )?) } - Owned(r) } - AggregateKind::Tuple(ty) => { - let layout = self.layout(&ty)?; - Owned(self.make_by_layout( - layout.size.bytes_usize(), - &layout, - None, - values, - locals, - )?) - } - AggregateKind::Union(x, f) => { - let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?; - let offset = layout - .fields - .offset(u32::from(f.local_id.into_raw()) as usize) - .bytes_usize(); - let op = self.eval_operand(&values[0], locals)?.get(&self)?; - let mut result = vec![0; layout.size.bytes_usize()]; - result[offset..offset + op.len()].copy_from_slice(op); - Owned(result) - } - AggregateKind::Adt(x, subst) => { - let (size, variant_layout, tag) = - self.layout_of_variant(*x, subst.clone(), locals)?; - Owned(self.make_by_layout(size, &variant_layout, tag, values, locals)?) - } - }, + } Rvalue::Cast(kind, operand, target_ty) => match kind { - CastKind::PointerExposeAddress => not_supported!("exposing pointer address"), - CastKind::PointerFromExposedAddress => { - not_supported!("creating pointer from exposed address") - } CastKind::Pointer(cast) => match cast { - PointerCast::Unsize => { + PointerCast::ReifyFnPointer | PointerCast::ClosureFnPointer(_) => { let current_ty = self.operand_ty(operand, locals)?; - match &target_ty.data(Interner).kind { - TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { - match &ty.data(Interner).kind { - TyKind::Slice(_) => match ¤t_ty.data(Interner).kind { - TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { - match &ty.data(Interner).kind { - TyKind::Array(_, size) => { - let addr = self - .eval_operand(operand, locals)? - .get(&self)?; - let len = const_as_usize(size); - let mut r = Vec::with_capacity(16); - r.extend(addr.iter().copied()); - r.extend(len.to_le_bytes().into_iter()); - Owned(r) - } - _ => { - not_supported!("slice unsizing from non arrays") - } - } - } - _ => not_supported!("slice unsizing from non pointers"), - }, - TyKind::Dyn(_) => not_supported!("dyn pointer unsize cast"), - _ => not_supported!("unknown unsized cast"), - } - } - _ => not_supported!("unsized cast on unknown pointer type"), + if let TyKind::FnDef(_, _) | TyKind::Closure(_, _) = + ¤t_ty.data(Interner).kind + { + let id = self.vtable_map.id(current_ty); + let ptr_size = self.ptr_size(); + Owned(id.to_le_bytes()[0..ptr_size].to_vec()) + } else { + not_supported!( + "creating a fn pointer from a non FnDef or Closure type" + ); } } - x => not_supported!("pointer cast {x:?}"), + PointerCast::Unsize => { + let current_ty = self.operand_ty(operand, locals)?; + let addr = self.eval_operand(operand, locals)?; + self.coerce_unsized(addr, ¤t_ty, target_ty)? + } + PointerCast::MutToConstPointer | PointerCast::UnsafeFnPointer => { + // This is no-op + Borrowed(self.eval_operand(operand, locals)?) + } + PointerCast::ArrayToPointer => { + // We should remove the metadata part if the current type is slice + Borrowed(self.eval_operand(operand, locals)?.slice(0..self.ptr_size())) + } }, CastKind::DynStar => not_supported!("dyn star cast"), - CastKind::IntToInt => { - // FIXME: handle signed cast - let current = pad16(self.eval_operand(operand, locals)?.get(&self)?, false); + CastKind::IntToInt + | CastKind::PointerExposeAddress + | CastKind::PointerFromExposedAddress => { + let current_ty = self.operand_ty(operand, locals)?; + let is_signed = match current_ty.kind(Interner) { + TyKind::Scalar(s) => match s { + chalk_ir::Scalar::Int(_) => true, + _ => false, + }, + _ => false, + }; + let current = pad16(self.eval_operand(operand, locals)?.get(&self)?, is_signed); let dest_size = self.size_of_sized(target_ty, locals, "destination of int to int cast")?; Owned(current[0..dest_size].to_vec()) @@ -846,31 +1224,106 @@ fn eval_rvalue<'a>( CastKind::FloatToInt => not_supported!("float to int cast"), CastKind::FloatToFloat => not_supported!("float to float cast"), CastKind::IntToFloat => not_supported!("float to int cast"), - CastKind::PtrToPtr => not_supported!("ptr to ptr cast"), CastKind::FnPtrToPtr => not_supported!("fn ptr to ptr cast"), }, }) } + fn coerce_unsized_look_through_fields( + &self, + ty: &Ty, + goal: impl Fn(&TyKind) -> Option, + ) -> Result { + let kind = ty.kind(Interner); + if let Some(x) = goal(kind) { + return Ok(x); + } + if let TyKind::Adt(id, subst) = kind { + if let AdtId::StructId(struct_id) = id.0 { + let field_types = self.db.field_types(struct_id.into()); + let mut field_types = field_types.iter(); + if let Some(ty) = + field_types.next().map(|x| x.1.clone().substitute(Interner, subst)) + { + return self.coerce_unsized_look_through_fields(&ty, goal); + } + } + } + Err(MirEvalError::CoerceUnsizedError(ty.clone())) + } + + fn coerce_unsized( + &mut self, + addr: Interval, + current_ty: &Ty, + target_ty: &Ty, + ) -> Result { + use IntervalOrOwned::*; + fn for_ptr(x: &TyKind) -> Option { + match x { + TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => Some(ty.clone()), + _ => None, + } + } + Ok(match self.coerce_unsized_look_through_fields(target_ty, for_ptr)? { + ty => match &ty.data(Interner).kind { + TyKind::Slice(_) => { + match self.coerce_unsized_look_through_fields(current_ty, for_ptr)? { + ty => match &ty.data(Interner).kind { + TyKind::Array(_, size) => { + let len = match try_const_usize(self.db, size) { + None => not_supported!( + "unevaluatble len of array in coerce unsized" + ), + Some(x) => x as usize, + }; + let mut r = Vec::with_capacity(16); + let addr = addr.get(self)?; + r.extend(addr.iter().copied()); + r.extend(len.to_le_bytes().into_iter()); + Owned(r) + } + t => { + not_supported!("slice unsizing from non array type {t:?}") + } + }, + } + } + TyKind::Dyn(_) => match ¤t_ty.data(Interner).kind { + TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { + let vtable = self.vtable_map.id(ty.clone()); + let mut r = Vec::with_capacity(16); + let addr = addr.get(self)?; + r.extend(addr.iter().copied()); + r.extend(vtable.to_le_bytes().into_iter()); + Owned(r) + } + _ => not_supported!("dyn unsizing from non pointers"), + }, + _ => not_supported!("unknown unsized cast"), + }, + }) + } + fn layout_of_variant( &mut self, x: VariantId, subst: Substitution, locals: &Locals<'_>, - ) -> Result<(usize, Layout, Option<(usize, usize, i128)>)> { + ) -> Result<(usize, Arc, Option<(usize, usize, i128)>)> { let adt = x.adt_id(); if let DefWithBodyId::VariantId(f) = locals.body.owner { if let VariantId::EnumVariantId(x) = x { if AdtId::from(f.parent) == adt { // Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and // infinite sized type errors) we use a dummy layout - let i = self.db.const_eval_discriminant(x)?; + let i = self.const_eval_discriminant(x)?; return Ok((16, self.layout(&TyBuilder::unit())?, Some((0, 16, i)))); } } } let layout = self.layout_adt(adt, subst)?; - Ok(match layout.variants { + Ok(match &layout.variants { Variants::Single { .. } => (layout.size.bytes_usize(), layout, None), Variants::Multiple { variants, tag, tag_encoding, .. } => { let cx = self @@ -882,18 +1335,27 @@ fn layout_of_variant( _ => not_supported!("multi variant layout for non-enums"), }; let rustc_enum_variant_idx = RustcEnumVariantIdx(enum_variant_id.local_id); - let mut discriminant = self.db.const_eval_discriminant(enum_variant_id)?; + let mut discriminant = self.const_eval_discriminant(enum_variant_id)?; let variant_layout = variants[rustc_enum_variant_idx].clone(); let have_tag = match tag_encoding { TagEncoding::Direct => true, TagEncoding::Niche { untagged_variant, niche_variants: _, niche_start } => { - discriminant = discriminant.wrapping_add(niche_start as i128); - untagged_variant != rustc_enum_variant_idx + if *untagged_variant == rustc_enum_variant_idx { + false + } else { + discriminant = (variants + .iter_enumerated() + .filter(|(x, _)| x != untagged_variant) + .position(|(x, _)| x == rustc_enum_variant_idx) + .unwrap() as i128) + .wrapping_add(*niche_start as i128); + true + } } }; ( layout.size.bytes_usize(), - variant_layout, + Arc::new(variant_layout), if have_tag { Some(( layout.fields.offset(0).bytes_usize(), @@ -910,74 +1372,87 @@ fn layout_of_variant( fn make_by_layout( &mut self, - size: usize, // Not neccessarily equal to variant_layout.size + size: usize, // Not necessarily equal to variant_layout.size variant_layout: &Layout, tag: Option<(usize, usize, i128)>, - values: &Vec, - locals: &Locals<'_>, + values: impl Iterator, ) -> Result> { let mut result = vec![0; size]; if let Some((offset, size, value)) = tag { result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]); } - for (i, op) in values.iter().enumerate() { + for (i, op) in values.enumerate() { let offset = variant_layout.fields.offset(i).bytes_usize(); - let op = self.eval_operand(op, locals)?.get(&self)?; + let op = op.get(&self)?; result[offset..offset + op.len()].copy_from_slice(op); } Ok(result) } - fn eval_operand(&mut self, x: &Operand, locals: &Locals<'_>) -> Result { + fn eval_operand(&mut self, x: &Operand, locals: &mut Locals<'_>) -> Result { Ok(match x { - Operand::Copy(p) | Operand::Move(p) => self.eval_place(p, locals)?, + Operand::Copy(p) | Operand::Move(p) => { + locals.drop_flags.remove_place(p); + self.eval_place(p, locals)? + } + Operand::Static(st) => { + let addr = self.eval_static(*st, locals)?; + Interval::new(addr, self.ptr_size()) + } Operand::Constant(konst) => { let data = &konst.data(Interner); match &data.value { - chalk_ir::ConstValue::BoundVar(b) => { - let c = locals - .subst - .as_slice(Interner) - .get(b.index) - .ok_or(MirEvalError::TypeError("missing generic arg"))? - .assert_const_ref(Interner); - self.eval_operand(&Operand::Constant(c.clone()), locals)? - } + chalk_ir::ConstValue::BoundVar(_) => not_supported!("bound var constant"), chalk_ir::ConstValue::InferenceVar(_) => { not_supported!("inference var constant") } chalk_ir::ConstValue::Placeholder(_) => not_supported!("placeholder constant"), - chalk_ir::ConstValue::Concrete(c) => match &c.interned { - ConstScalar::Bytes(v, memory_map) => { - let mut v: Cow<'_, [u8]> = Cow::Borrowed(v); - let patch_map = memory_map.transform_addresses(|b| { - let addr = self.heap_allocate(b.len()); - self.write_memory(addr, b)?; - Ok(addr.to_usize()) - })?; - let size = self.size_of(&data.ty, locals)?.unwrap_or(v.len()); - if size != v.len() { - // Handle self enum - if size == 16 && v.len() < 16 { - v = Cow::Owned(pad16(&v, false).to_vec()); - } else if size < 16 && v.len() == 16 { - v = Cow::Owned(v[0..size].to_vec()); - } else { - return Err(MirEvalError::InvalidConst(konst.clone())); - } - } - let addr = self.heap_allocate(size); - self.write_memory(addr, &v)?; - self.patch_addresses(&patch_map, addr, &data.ty, locals)?; - Interval::new(addr, size) - } - ConstScalar::Unknown => not_supported!("evaluating unknown const"), - }, + chalk_ir::ConstValue::Concrete(c) => { + self.allocate_const_in_heap(c, &data.ty, locals, konst)? + } } } }) } + fn allocate_const_in_heap( + &mut self, + c: &chalk_ir::ConcreteConst, + ty: &Ty, + locals: &Locals<'_>, + konst: &chalk_ir::Const, + ) -> Result { + Ok(match &c.interned { + ConstScalar::Bytes(v, memory_map) => { + let mut v: Cow<'_, [u8]> = Cow::Borrowed(v); + let patch_map = memory_map.transform_addresses(|b| { + let addr = self.heap_allocate(b.len(), 1); // FIXME: align is wrong + self.write_memory(addr, b)?; + Ok(addr.to_usize()) + })?; + let (size, align) = self.size_align_of(ty, locals)?.unwrap_or((v.len(), 1)); + if size != v.len() { + // Handle self enum + if size == 16 && v.len() < 16 { + v = Cow::Owned(pad16(&v, false).to_vec()); + } else if size < 16 && v.len() == 16 { + v = Cow::Owned(v[0..size].to_vec()); + } else { + return Err(MirEvalError::InvalidConst(konst.clone())); + } + } + let addr = self.heap_allocate(size, align); + self.write_memory(addr, &v)?; + self.patch_addresses(&patch_map, &memory_map.vtable, addr, ty, locals)?; + Interval::new(addr, size) + } + ConstScalar::UnevaluatedConst(..) => { + not_supported!("unevaluated const present in monomorphized mir"); + } + ConstScalar::Unknown => not_supported!("evaluating unknown const"), + }) + } + fn eval_place(&mut self, p: &Place, locals: &Locals<'_>) -> Result { let addr = self.place_addr(p, locals)?; Ok(Interval::new( @@ -987,197 +1462,205 @@ fn eval_place(&mut self, p: &Place, locals: &Locals<'_>) -> Result { } fn read_memory(&self, addr: Address, size: usize) -> Result<&[u8]> { + if size == 0 { + return Ok(&[]); + } let (mem, pos) = match addr { Stack(x) => (&self.stack, x), Heap(x) => (&self.heap, x), + Invalid(x) => { + return Err(MirEvalError::UndefinedBehavior(format!( + "read invalid memory address {x} with size {size}" + ))); + } }; - mem.get(pos..pos + size).ok_or(MirEvalError::UndefinedBehavior("out of bound memory read")) + mem.get(pos..pos + size) + .ok_or_else(|| MirEvalError::UndefinedBehavior("out of bound memory read".to_string())) } fn write_memory(&mut self, addr: Address, r: &[u8]) -> Result<()> { + if r.is_empty() { + return Ok(()); + } let (mem, pos) = match addr { Stack(x) => (&mut self.stack, x), Heap(x) => (&mut self.heap, x), + Invalid(x) => { + return Err(MirEvalError::UndefinedBehavior(format!( + "write invalid memory address {x} with content {r:?}" + ))); + } }; mem.get_mut(pos..pos + r.len()) - .ok_or(MirEvalError::UndefinedBehavior("out of bound memory write"))? + .ok_or_else(|| { + MirEvalError::UndefinedBehavior("out of bound memory write".to_string()) + })? .copy_from_slice(r); Ok(()) } - fn size_of(&self, ty: &Ty, locals: &Locals<'_>) -> Result> { + fn size_align_of(&self, ty: &Ty, locals: &Locals<'_>) -> Result> { if let DefWithBodyId::VariantId(f) = locals.body.owner { if let Some((adt, _)) = ty.as_adt() { if AdtId::from(f.parent) == adt { // Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and // infinite sized type errors) we use a dummy size - return Ok(Some(16)); + return Ok(Some((16, 16))); } } } - let ty = &self.ty_filler(ty, locals.subst, locals.body.owner)?; let layout = self.layout(ty); if self.assert_placeholder_ty_is_unused { if matches!(layout, Err(MirEvalError::LayoutError(LayoutError::HasPlaceholder, _))) { - return Ok(Some(0)); + return Ok(Some((0, 1))); } } let layout = layout?; - Ok(layout.is_sized().then(|| layout.size.bytes_usize())) + Ok(layout + .is_sized() + .then(|| (layout.size.bytes_usize(), layout.align.abi.bytes() as usize))) } /// A version of `self.size_of` which returns error if the type is unsized. `what` argument should /// be something that complete this: `error: type {ty} was unsized. {what} should be sized` fn size_of_sized(&self, ty: &Ty, locals: &Locals<'_>, what: &'static str) -> Result { - match self.size_of(ty, locals)? { - Some(x) => Ok(x), + match self.size_align_of(ty, locals)? { + Some(x) => Ok(x.0), None => Err(MirEvalError::TypeIsUnsized(ty.clone(), what)), } } - /// Uses `ty_filler` to fill an entire subst - fn subst_filler(&self, subst: &Substitution, locals: &Locals<'_>) -> Substitution { - Substitution::from_iter( - Interner, - subst.iter(Interner).map(|x| match x.data(Interner) { - chalk_ir::GenericArgData::Ty(ty) => { - let Ok(ty) = self.ty_filler(ty, locals.subst, locals.body.owner) else { - return x.clone(); - }; - chalk_ir::GenericArgData::Ty(ty).intern(Interner) - } - _ => x.clone(), - }), - ) - } - - /// This function substitutes placeholders of the body with the provided subst, effectively plays - /// the rule of monomorphization. In addition to placeholders, it substitutes opaque types (return - /// position impl traits) with their underlying type. - fn ty_filler(&self, ty: &Ty, subst: &Substitution, owner: DefWithBodyId) -> Result { - struct Filler<'a> { - db: &'a dyn HirDatabase, - subst: &'a Substitution, - skip_params: usize, - } - impl FallibleTypeFolder for Filler<'_> { - type Error = MirEvalError; - - fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder { - self - } - - fn interner(&self) -> Interner { - Interner - } - - fn try_fold_ty( - &mut self, - ty: Ty, - outer_binder: DebruijnIndex, - ) -> std::result::Result { - match ty.kind(Interner) { - TyKind::OpaqueType(id, subst) => { - let impl_trait_id = self.db.lookup_intern_impl_trait_id((*id).into()); - match impl_trait_id { - crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { - let infer = self.db.infer(func.into()); - let filler = &mut Filler { db: self.db, subst, skip_params: 0 }; - filler.try_fold_ty(infer.type_of_rpit[idx].clone(), outer_binder) - } - crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { - not_supported!("async block impl trait"); - } - } - } - _ => ty.try_super_fold_with(self.as_dyn(), outer_binder), - } - } - - fn try_fold_free_placeholder_ty( - &mut self, - idx: chalk_ir::PlaceholderIndex, - _outer_binder: DebruijnIndex, - ) -> std::result::Result { - let x = from_placeholder_idx(self.db, idx); - Ok(self - .subst - .as_slice(Interner) - .get((u32::from(x.local_id.into_raw()) as usize) + self.skip_params) - .and_then(|x| x.ty(Interner)) - .ok_or(MirEvalError::TypeError("Generic arg not provided"))? - .clone()) - } - } - let filler = &mut Filler { db: self.db, subst, skip_params: 0 }; - Ok(normalize(self.db, owner, ty.clone().try_fold_with(filler, DebruijnIndex::INNERMOST)?)) - } - - fn heap_allocate(&mut self, s: usize) -> Address { + fn heap_allocate(&mut self, size: usize, _align: usize) -> Address { let pos = self.heap.len(); - self.heap.extend(iter::repeat(0).take(s)); + self.heap.extend(iter::repeat(0).take(size)); Address::Heap(pos) } - pub fn interpret_mir_with_no_arg(&mut self, body: &MirBody) -> Result> { - self.interpret_mir(&body, vec![].into_iter(), Substitution::empty(Interner)) - } - - fn detect_lang_function(&self, def: FunctionId) -> Option { - let candidate = lang_attr(self.db.upcast(), def)?; - // filter normal lang functions out - if [LangItem::IntoIterIntoIter, LangItem::IteratorNext].contains(&candidate) { + fn detect_fn_trait(&self, def: FunctionId) -> Option { + use LangItem::*; + let ItemContainerId::TraitId(parent) = self.db.lookup_intern_function(def).container else { return None; + }; + let l = lang_attr(self.db.upcast(), parent)?; + match l { + FnOnce => Some(FnTrait::FnOnce), + FnMut => Some(FnTrait::FnMut), + Fn => Some(FnTrait::Fn), + _ => None, } - Some(candidate) } fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result { - // FIXME: support indirect references - let mut mm = MemoryMap::default(); - match ty.kind(Interner) { - TyKind::Ref(_, _, t) => { - let size = self.size_of(t, locals)?; - match size { - Some(size) => { - let addr_usize = from_bytes!(usize, bytes); - mm.insert( - addr_usize, - self.read_memory(Address::from_usize(addr_usize), size)?.to_vec(), - ) - } - None => { - let element_size = match t.kind(Interner) { - TyKind::Str => 1, - TyKind::Slice(t) => { - self.size_of_sized(t, locals, "slice inner type")? + fn rec( + this: &Evaluator<'_>, + bytes: &[u8], + ty: &Ty, + locals: &Locals<'_>, + mm: &mut MemoryMap, + ) -> Result<()> { + match ty.kind(Interner) { + TyKind::Ref(_, _, t) => { + let size = this.size_align_of(t, locals)?; + match size { + Some((size, _)) => { + let addr_usize = from_bytes!(usize, bytes); + mm.insert( + addr_usize, + this.read_memory(Address::from_usize(addr_usize), size)?.to_vec(), + ) + } + None => { + let mut check_inner = None; + let element_size = match t.kind(Interner) { + TyKind::Str => 1, + TyKind::Slice(t) => { + check_inner = Some(t); + this.size_of_sized(t, locals, "slice inner type")? + } + _ => return Ok(()), // FIXME: support other kind of unsized types + }; + let (addr, meta) = bytes.split_at(bytes.len() / 2); + let count = from_bytes!(usize, meta); + let size = element_size * count; + let addr = Address::from_bytes(addr)?; + let b = this.read_memory(addr, size)?; + mm.insert(addr.to_usize(), b.to_vec()); + if let Some(ty) = check_inner { + for i in 0..count { + let offset = element_size * i; + rec(this, &b[offset..offset + element_size], ty, locals, mm)?; + } } - _ => return Ok(mm), // FIXME: support other kind of unsized types - }; - let (addr, meta) = bytes.split_at(bytes.len() / 2); - let size = element_size * from_bytes!(usize, meta); - let addr = Address::from_bytes(addr)?; - mm.insert(addr.to_usize(), self.read_memory(addr, size)?.to_vec()); + } } } + chalk_ir::TyKind::Tuple(_, subst) => { + let layout = this.layout(ty)?; + for (id, ty) in subst.iter(Interner).enumerate() { + let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument + let offset = layout.fields.offset(id).bytes_usize(); + let size = this.layout(ty)?.size.bytes_usize(); + rec(this, &bytes[offset..offset + size], ty, locals, mm)?; + } + } + chalk_ir::TyKind::Adt(adt, subst) => match adt.0 { + AdtId::StructId(s) => { + let data = this.db.struct_data(s); + let layout = this.layout(ty)?; + let field_types = this.db.field_types(s.into()); + for (f, _) in data.variant_data.fields().iter() { + let offset = layout + .fields + .offset(u32::from(f.into_raw()) as usize) + .bytes_usize(); + let ty = &field_types[f].clone().substitute(Interner, subst); + let size = this.layout(ty)?.size.bytes_usize(); + rec(this, &bytes[offset..offset + size], ty, locals, mm)?; + } + } + AdtId::EnumId(e) => { + let layout = this.layout(ty)?; + if let Some((v, l)) = + detect_variant_from_bytes(&layout, this.db, this.crate_id, bytes, e) + { + let data = &this.db.enum_data(e).variants[v].variant_data; + let field_types = this + .db + .field_types(EnumVariantId { parent: e, local_id: v }.into()); + for (f, _) in data.fields().iter() { + let offset = + l.fields.offset(u32::from(f.into_raw()) as usize).bytes_usize(); + let ty = &field_types[f].clone().substitute(Interner, subst); + let size = this.layout(ty)?.size.bytes_usize(); + rec(this, &bytes[offset..offset + size], ty, locals, mm)?; + } + } + } + AdtId::UnionId(_) => (), + }, + _ => (), } - _ => (), + Ok(()) } + let mut mm = MemoryMap::default(); + rec(self, bytes, ty, locals, &mut mm)?; Ok(mm) } fn patch_addresses( &mut self, patch_map: &HashMap, + old_vtable: &VTableMap, addr: Address, ty: &Ty, locals: &Locals<'_>, ) -> Result<()> { // FIXME: support indirect references + let layout = self.layout(ty)?; let my_size = self.size_of_sized(ty, locals, "value to patch address")?; match ty.kind(Interner) { TyKind::Ref(_, _, t) => { - let size = self.size_of(t, locals)?; + let size = self.size_align_of(t, locals)?; match size { Some(_) => { let current = from_bytes!(usize, self.read_memory(addr, my_size)?); @@ -1193,50 +1676,440 @@ fn patch_addresses( } } } - _ => (), + TyKind::Function(_) => { + let ty = old_vtable.ty_of_bytes(self.read_memory(addr, my_size)?)?.clone(); + let new_id = self.vtable_map.id(ty); + self.write_memory(addr, &new_id.to_le_bytes())?; + } + TyKind::Adt(id, subst) => match id.0 { + AdtId::StructId(s) => { + for (i, (_, ty)) in self.db.field_types(s.into()).iter().enumerate() { + let offset = layout.fields.offset(i).bytes_usize(); + let ty = ty.clone().substitute(Interner, subst); + self.patch_addresses( + patch_map, + old_vtable, + addr.offset(offset), + &ty, + locals, + )?; + } + } + AdtId::UnionId(_) => (), + AdtId::EnumId(_) => (), + }, + TyKind::AssociatedType(_, _) + | TyKind::Scalar(_) + | TyKind::Tuple(_, _) + | TyKind::Array(_, _) + | TyKind::Slice(_) + | TyKind::Raw(_, _) + | TyKind::OpaqueType(_, _) + | TyKind::FnDef(_, _) + | TyKind::Str + | TyKind::Never + | TyKind::Closure(_, _) + | TyKind::Generator(_, _) + | TyKind::GeneratorWitness(_, _) + | TyKind::Foreign(_) + | TyKind::Error + | TyKind::Placeholder(_) + | TyKind::Dyn(_) + | TyKind::Alias(_) + | TyKind::BoundVar(_) + | TyKind::InferenceVar(_, _) => (), } Ok(()) } - fn exec_intrinsic( - &self, - as_str: &str, - _arg_bytes: impl Iterator>, + fn exec_fn_pointer( + &mut self, + bytes: Interval, + destination: Interval, + args: &[IntervalAndTy], + locals: &Locals<'_>, + span: MirSpan, + ) -> Result<()> { + let id = from_bytes!(usize, bytes.get(self)?); + let next_ty = self.vtable_map.ty(id)?.clone(); + match &next_ty.data(Interner).kind { + TyKind::FnDef(def, generic_args) => { + self.exec_fn_def(*def, generic_args, destination, args, &locals, span)?; + } + TyKind::Closure(id, subst) => { + self.exec_closure(*id, bytes.slice(0..0), subst, destination, args, locals, span)?; + } + _ => return Err(MirEvalError::TypeError("function pointer to non function")), + } + Ok(()) + } + + fn exec_closure( + &mut self, + closure: ClosureId, + closure_data: Interval, + generic_args: &Substitution, + destination: Interval, + args: &[IntervalAndTy], + locals: &Locals<'_>, + span: MirSpan, + ) -> Result<()> { + let mir_body = self + .db + .monomorphized_mir_body_for_closure( + closure, + generic_args.clone(), + self.trait_env.clone(), + ) + .map_err(|x| MirEvalError::MirLowerErrorForClosure(closure, x))?; + let closure_data = if mir_body.locals[mir_body.param_locals[0]].ty.as_reference().is_some() + { + closure_data.addr.to_bytes() + } else { + closure_data.get(self)?.to_owned() + }; + let arg_bytes = iter::once(Ok(closure_data)) + .chain(args.iter().map(|x| Ok(x.get(&self)?.to_owned()))) + .collect::>>()?; + let bytes = self.interpret_mir(&mir_body, arg_bytes.into_iter()).map_err(|e| { + MirEvalError::InFunction(Either::Right(closure), Box::new(e), span, locals.body.owner) + })?; + destination.write_from_bytes(self, &bytes) + } + + fn exec_fn_def( + &mut self, + def: FnDefId, + generic_args: &Substitution, + destination: Interval, + args: &[IntervalAndTy], + locals: &Locals<'_>, + span: MirSpan, + ) -> Result<()> { + let def: CallableDefId = from_chalk(self.db, def); + let generic_args = generic_args.clone(); + match def { + CallableDefId::FunctionId(def) => { + if let Some(_) = self.detect_fn_trait(def) { + self.exec_fn_trait(&args, destination, locals, span)?; + return Ok(()); + } + self.exec_fn_with_args(def, args, generic_args, locals, destination, span)?; + } + CallableDefId::StructId(id) => { + let (size, variant_layout, tag) = + self.layout_of_variant(id.into(), generic_args, &locals)?; + let result = self.make_by_layout( + size, + &variant_layout, + tag, + args.iter().map(|x| x.interval.into()), + )?; + destination.write_from_bytes(self, &result)?; + } + CallableDefId::EnumVariantId(id) => { + let (size, variant_layout, tag) = + self.layout_of_variant(id.into(), generic_args, &locals)?; + let result = self.make_by_layout( + size, + &variant_layout, + tag, + args.iter().map(|x| x.interval.into()), + )?; + destination.write_from_bytes(self, &result)?; + } + } + Ok(()) + } + + fn exec_fn_with_args( + &mut self, + def: FunctionId, + args: &[IntervalAndTy], generic_args: Substitution, locals: &Locals<'_>, - ) -> Result> { - match as_str { - "size_of" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { - return Err(MirEvalError::TypeError("size_of generic arg is not provided")); - }; - let size = self.size_of(ty, locals)?; - match size { - Some(x) => Ok(x.to_le_bytes().to_vec()), - None => return Err(MirEvalError::TypeError("size_of arg is unsized")), - } + destination: Interval, + span: MirSpan, + ) -> Result<()> { + if self.detect_and_exec_special_function( + def, + args, + &generic_args, + locals, + destination, + span, + )? { + return Ok(()); + } + let arg_bytes = + args.iter().map(|x| Ok(x.get(&self)?.to_owned())).collect::>>()?; + if let Some(self_ty_idx) = + is_dyn_method(self.db, self.trait_env.clone(), def, generic_args.clone()) + { + // In the layout of current possible receiver, which at the moment of writing this code is one of + // `&T`, `&mut T`, `Box`, `Rc`, `Arc`, and `Pin

` where `P` is one of possible recievers, + // the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on + // the type. + let ty = + self.vtable_map.ty_of_bytes(&arg_bytes[0][self.ptr_size()..self.ptr_size() * 2])?; + let mut args_for_target = args.to_vec(); + args_for_target[0] = IntervalAndTy { + interval: args_for_target[0].interval.slice(0..self.ptr_size()), + ty: ty.clone(), + }; + let ty = GenericArgData::Ty(ty.clone()).intern(Interner); + let generics_for_target = + Substitution::from_iter( + Interner, + generic_args.iter(Interner).enumerate().map(|(i, x)| { + if i == self_ty_idx { + &ty + } else { + x + } + }), + ); + return self.exec_fn_with_args( + def, + &args_for_target, + generics_for_target, + locals, + destination, + span, + ); + } + let (imp, generic_args) = + lookup_impl_method(self.db, self.trait_env.clone(), def, generic_args); + self.exec_looked_up_function(generic_args, locals, imp, arg_bytes, span, destination) + } + + fn exec_looked_up_function( + &mut self, + generic_args: Substitution, + locals: &Locals<'_>, + imp: FunctionId, + arg_bytes: Vec>, + span: MirSpan, + destination: Interval, + ) -> Result<()> { + let def = imp.into(); + let mir_body = self + .db + .monomorphized_mir_body(def, generic_args, self.trait_env.clone()) + .map_err(|e| { + MirEvalError::InFunction( + Either::Left(imp), + Box::new(MirEvalError::MirLowerError(imp, e)), + span, + locals.body.owner, + ) + })?; + let result = self.interpret_mir(&mir_body, arg_bytes.iter().cloned()).map_err(|e| { + MirEvalError::InFunction(Either::Left(imp), Box::new(e), span, locals.body.owner) + })?; + destination.write_from_bytes(self, &result)?; + Ok(()) + } + + fn exec_fn_trait( + &mut self, + args: &[IntervalAndTy], + destination: Interval, + locals: &Locals<'_>, + span: MirSpan, + ) -> Result<()> { + let func = args.get(0).ok_or(MirEvalError::TypeError("fn trait with no arg"))?; + let mut func_ty = func.ty.clone(); + let mut func_data = func.interval; + while let TyKind::Ref(_, _, z) = func_ty.kind(Interner) { + func_ty = z.clone(); + if matches!(func_ty.kind(Interner), TyKind::Dyn(_)) { + let id = + from_bytes!(usize, &func_data.get(self)?[self.ptr_size()..self.ptr_size() * 2]); + func_data = func_data.slice(0..self.ptr_size()); + func_ty = self.vtable_map.ty(id)?.clone(); + } + let size = self.size_of_sized(&func_ty, locals, "self type of fn trait")?; + func_data = Interval { addr: Address::from_bytes(func_data.get(self)?)?, size }; + } + match &func_ty.data(Interner).kind { + TyKind::FnDef(def, subst) => { + self.exec_fn_def(*def, subst, destination, &args[1..], locals, span)?; + } + TyKind::Function(_) => { + self.exec_fn_pointer(func_data, destination, &args[1..], locals, span)?; + } + TyKind::Closure(closure, subst) => { + self.exec_closure( + *closure, + func_data, + &Substitution::from_iter(Interner, ClosureSubst(subst).parent_subst()), + destination, + &args[1..], + locals, + span, + )?; + } + x => not_supported!("Call FnTrait methods with type {x:?}"), + } + Ok(()) + } + + fn eval_static(&mut self, st: StaticId, locals: &Locals<'_>) -> Result

{ + if let Some(o) = self.static_locations.get(&st) { + return Ok(*o); + }; + let static_data = self.db.static_data(st); + let result = if !static_data.is_extern { + let konst = self.db.const_eval_static(st).map_err(|e| { + MirEvalError::ConstEvalError( + static_data.name.as_str().unwrap_or("_").to_owned(), + Box::new(e), + ) + })?; + let data = &konst.data(Interner); + if let chalk_ir::ConstValue::Concrete(c) = &data.value { + self.allocate_const_in_heap(&c, &data.ty, locals, &konst)? + } else { + not_supported!("unevaluatable static"); + } + } else { + let ty = &self.db.infer(st.into())[self.db.body(st.into()).body_expr]; + let Some((size, align)) = self.size_align_of(&ty, locals)? else { + not_supported!("unsized extern static"); + }; + let addr = self.heap_allocate(size, align); + Interval::new(addr, size) + }; + let addr = self.heap_allocate(self.ptr_size(), self.ptr_size()); + self.write_memory(addr, &result.addr.to_bytes())?; + self.static_locations.insert(st, addr); + Ok(addr) + } + + fn const_eval_discriminant(&self, variant: EnumVariantId) -> Result { + let r = self.db.const_eval_discriminant(variant); + match r { + Ok(r) => Ok(r), + Err(e) => { + let data = self.db.enum_data(variant.parent); + let name = format!( + "{}::{}", + data.name.display(self.db.upcast()), + data.variants[variant.local_id].name.display(self.db.upcast()) + ); + Err(MirEvalError::ConstEvalError(name, Box::new(e))) } - _ => not_supported!("unknown intrinsic {as_str}"), } } - pub(crate) fn exec_lang_item( - &self, - x: LangItem, - mut args: std::vec::IntoIter>, - ) -> Result> { - use LangItem::*; - match x { - PanicFmt | BeginPanic => Err(MirEvalError::Panic), - SliceLen => { - let arg = args - .next() - .ok_or(MirEvalError::TypeError("argument of <[T]>::len() is not provided"))?; - let ptr_size = arg.len() / 2; - Ok(arg[ptr_size..].into()) - } - x => not_supported!("Executing lang item {x:?}"), + fn drop_place(&mut self, place: &Place, locals: &mut Locals<'_>, span: MirSpan) -> Result<()> { + let (addr, ty, metadata) = self.place_addr_and_ty_and_metadata(place, locals)?; + if !locals.drop_flags.remove_place(place) { + return Ok(()); } + let metadata = match metadata { + Some(x) => x.get(self)?.to_vec(), + None => vec![], + }; + self.run_drop_glue_deep(ty, locals, addr, &metadata, span) + } + + fn run_drop_glue_deep( + &mut self, + ty: Ty, + locals: &Locals<'_>, + addr: Address, + _metadata: &[u8], + span: MirSpan, + ) -> Result<()> { + let Some(drop_fn) = (|| { + let drop_trait = self.db.lang_item(self.crate_id, LangItem::Drop)?.as_trait()?; + self.db.trait_data(drop_trait).method_by_name(&name![drop]) + })() else { + // in some tests we don't have drop trait in minicore, and + // we can ignore drop in them. + return Ok(()); + }; + let (impl_drop_candidate, subst) = lookup_impl_method( + self.db, + self.trait_env.clone(), + drop_fn, + Substitution::from1(Interner, ty.clone()), + ); + if impl_drop_candidate != drop_fn { + self.exec_looked_up_function( + subst, + locals, + impl_drop_candidate, + vec![addr.to_bytes()], + span, + Interval { addr: Address::Invalid(0), size: 0 }, + )?; + } + match ty.kind(Interner) { + TyKind::Adt(id, subst) => { + match id.0 { + AdtId::StructId(s) => { + let data = self.db.struct_data(s); + if data.flags.contains(StructFlags::IS_MANUALLY_DROP) { + return Ok(()); + } + let layout = self.layout_adt(id.0, subst.clone())?; + match data.variant_data.as_ref() { + VariantData::Record(fields) | VariantData::Tuple(fields) => { + let field_types = self.db.field_types(s.into()); + for (field, _) in fields.iter() { + let offset = layout + .fields + .offset(u32::from(field.into_raw()) as usize) + .bytes_usize(); + let addr = addr.offset(offset); + let ty = field_types[field].clone().substitute(Interner, subst); + self.run_drop_glue_deep(ty, locals, addr, &[], span)?; + } + } + VariantData::Unit => (), + } + } + AdtId::UnionId(_) => (), // union fields don't need drop + AdtId::EnumId(_) => (), + } + } + TyKind::AssociatedType(_, _) + | TyKind::Scalar(_) + | TyKind::Tuple(_, _) + | TyKind::Array(_, _) + | TyKind::Slice(_) + | TyKind::Raw(_, _) + | TyKind::Ref(_, _, _) + | TyKind::OpaqueType(_, _) + | TyKind::FnDef(_, _) + | TyKind::Str + | TyKind::Never + | TyKind::Closure(_, _) + | TyKind::Generator(_, _) + | TyKind::GeneratorWitness(_, _) + | TyKind::Foreign(_) + | TyKind::Error + | TyKind::Placeholder(_) + | TyKind::Dyn(_) + | TyKind::Alias(_) + | TyKind::Function(_) + | TyKind::BoundVar(_) + | TyKind::InferenceVar(_, _) => (), + }; + Ok(()) + } + + fn write_to_stdout(&mut self, interval: Interval) -> Result<()> { + self.stdout.extend(interval.get(self)?.to_vec()); + Ok(()) + } + + fn write_to_stderr(&mut self, interval: Interval) -> Result<()> { + self.stderr.extend(interval.get(self)?.to_vec()); + Ok(()) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs new file mode 100644 index 000000000000..3b9ef03c369f --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs @@ -0,0 +1,792 @@ +//! Interpret intrinsics, lang items and `extern "C"` wellknown functions which their implementation +//! is not available. + +use std::cmp; + +use super::*; + +macro_rules! from_bytes { + ($ty:tt, $value:expr) => { + ($ty::from_le_bytes(match ($value).try_into() { + Ok(x) => x, + Err(_) => return Err(MirEvalError::TypeError("mismatched size")), + })) + }; +} + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirEvalError::NotSupported(format!($x))) + }; +} + +impl Evaluator<'_> { + pub(super) fn detect_and_exec_special_function( + &mut self, + def: FunctionId, + args: &[IntervalAndTy], + generic_args: &Substitution, + locals: &Locals<'_>, + destination: Interval, + span: MirSpan, + ) -> Result { + let function_data = self.db.function_data(def); + let is_intrinsic = match &function_data.abi { + Some(abi) => *abi == Interned::new_str("rust-intrinsic"), + None => match def.lookup(self.db.upcast()).container { + hir_def::ItemContainerId::ExternBlockId(block) => { + let id = block.lookup(self.db.upcast()).id; + id.item_tree(self.db.upcast())[id.value].abi.as_deref() + == Some("rust-intrinsic") + } + _ => false, + }, + }; + if is_intrinsic { + self.exec_intrinsic( + function_data.name.as_text().unwrap_or_default().as_str(), + args, + generic_args, + destination, + &locals, + span, + )?; + return Ok(true); + } + let is_extern_c = match def.lookup(self.db.upcast()).container { + hir_def::ItemContainerId::ExternBlockId(block) => { + let id = block.lookup(self.db.upcast()).id; + id.item_tree(self.db.upcast())[id.value].abi.as_deref() == Some("C") + } + _ => false, + }; + if is_extern_c { + self.exec_extern_c( + function_data.name.as_text().unwrap_or_default().as_str(), + args, + generic_args, + destination, + &locals, + span, + )?; + return Ok(true); + } + let alloc_fn = function_data + .attrs + .iter() + .filter_map(|x| x.path().as_ident()) + .filter_map(|x| x.as_str()) + .find(|x| { + [ + "rustc_allocator", + "rustc_deallocator", + "rustc_reallocator", + "rustc_allocator_zeroed", + ] + .contains(x) + }); + if let Some(alloc_fn) = alloc_fn { + self.exec_alloc_fn(alloc_fn, args, destination)?; + return Ok(true); + } + if let Some(x) = self.detect_lang_function(def) { + let arg_bytes = + args.iter().map(|x| Ok(x.get(&self)?.to_owned())).collect::>>()?; + let result = self.exec_lang_item(x, generic_args, &arg_bytes, locals, span)?; + destination.write_from_bytes(self, &result)?; + return Ok(true); + } + Ok(false) + } + + fn exec_alloc_fn( + &mut self, + alloc_fn: &str, + args: &[IntervalAndTy], + destination: Interval, + ) -> Result<()> { + match alloc_fn { + "rustc_allocator_zeroed" | "rustc_allocator" => { + let [size, align] = args else { + return Err(MirEvalError::TypeError("rustc_allocator args are not provided")); + }; + let size = from_bytes!(usize, size.get(self)?); + let align = from_bytes!(usize, align.get(self)?); + let result = self.heap_allocate(size, align); + destination.write_from_bytes(self, &result.to_bytes())?; + } + "rustc_deallocator" => { /* no-op for now */ } + "rustc_reallocator" => { + let [ptr, old_size, align, new_size] = args else { + return Err(MirEvalError::TypeError("rustc_allocator args are not provided")); + }; + let ptr = Address::from_bytes(ptr.get(self)?)?; + let old_size = from_bytes!(usize, old_size.get(self)?); + let new_size = from_bytes!(usize, new_size.get(self)?); + let align = from_bytes!(usize, align.get(self)?); + let result = self.heap_allocate(new_size, align); + Interval { addr: result, size: old_size } + .write_from_interval(self, Interval { addr: ptr, size: old_size })?; + destination.write_from_bytes(self, &result.to_bytes())?; + } + _ => not_supported!("unknown alloc function"), + } + Ok(()) + } + + fn detect_lang_function(&self, def: FunctionId) -> Option { + use LangItem::*; + let candidate = lang_attr(self.db.upcast(), def)?; + // We want to execute these functions with special logic + if [PanicFmt, BeginPanic, SliceLen, DropInPlace].contains(&candidate) { + return Some(candidate); + } + None + } + + fn exec_lang_item( + &mut self, + x: LangItem, + generic_args: &Substitution, + args: &[Vec], + locals: &Locals<'_>, + span: MirSpan, + ) -> Result> { + use LangItem::*; + let mut args = args.iter(); + match x { + BeginPanic => Err(MirEvalError::Panic("".to_string())), + PanicFmt => { + let message = (|| { + let arguments_struct = + self.db.lang_item(self.crate_id, LangItem::FormatArguments)?.as_struct()?; + let arguments_layout = self + .layout_adt(arguments_struct.into(), Substitution::empty(Interner)) + .ok()?; + let arguments_field_pieces = + self.db.struct_data(arguments_struct).variant_data.field(&name![pieces])?; + let pieces_offset = arguments_layout + .fields + .offset(u32::from(arguments_field_pieces.into_raw()) as usize) + .bytes_usize(); + let ptr_size = self.ptr_size(); + let arg = args.next()?; + let pieces_array_addr = + Address::from_bytes(&arg[pieces_offset..pieces_offset + ptr_size]).ok()?; + let pieces_array_len = usize::from_le_bytes( + (&arg[pieces_offset + ptr_size..pieces_offset + 2 * ptr_size]) + .try_into() + .ok()?, + ); + let mut message = "".to_string(); + for i in 0..pieces_array_len { + let piece_ptr_addr = pieces_array_addr.offset(2 * i * ptr_size); + let piece_addr = + Address::from_bytes(self.read_memory(piece_ptr_addr, ptr_size).ok()?) + .ok()?; + let piece_len = usize::from_le_bytes( + self.read_memory(piece_ptr_addr.offset(ptr_size), ptr_size) + .ok()? + .try_into() + .ok()?, + ); + let piece_data = self.read_memory(piece_addr, piece_len).ok()?; + message += &std::string::String::from_utf8_lossy(piece_data); + } + Some(message) + })() + .unwrap_or_else(|| "".to_string()); + Err(MirEvalError::Panic(message)) + } + SliceLen => { + let arg = args + .next() + .ok_or(MirEvalError::TypeError("argument of <[T]>::len() is not provided"))?; + let ptr_size = arg.len() / 2; + Ok(arg[ptr_size..].into()) + } + DropInPlace => { + let ty = + generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)).ok_or( + MirEvalError::TypeError( + "generic argument of drop_in_place is not provided", + ), + )?; + let arg = args + .next() + .ok_or(MirEvalError::TypeError("argument of drop_in_place is not provided"))?; + self.run_drop_glue_deep( + ty.clone(), + locals, + Address::from_bytes(&arg[0..self.ptr_size()])?, + &arg[self.ptr_size()..], + span, + )?; + Ok(vec![]) + } + x => not_supported!("Executing lang item {x:?}"), + } + } + + fn exec_extern_c( + &mut self, + as_str: &str, + args: &[IntervalAndTy], + _generic_args: &Substitution, + destination: Interval, + locals: &Locals<'_>, + _span: MirSpan, + ) -> Result<()> { + match as_str { + "memcmp" => { + let [ptr1, ptr2, size] = args else { + return Err(MirEvalError::TypeError("memcmp args are not provided")); + }; + let addr1 = Address::from_bytes(ptr1.get(self)?)?; + let addr2 = Address::from_bytes(ptr2.get(self)?)?; + let size = from_bytes!(usize, size.get(self)?); + let slice1 = self.read_memory(addr1, size)?; + let slice2 = self.read_memory(addr2, size)?; + let r: i128 = match slice1.cmp(slice2) { + cmp::Ordering::Less => -1, + cmp::Ordering::Equal => 0, + cmp::Ordering::Greater => 1, + }; + destination.write_from_bytes(self, &r.to_le_bytes()[..destination.size]) + } + "write" => { + let [fd, ptr, len] = args else { + return Err(MirEvalError::TypeError("libc::write args are not provided")); + }; + let fd = u128::from_le_bytes(pad16(fd.get(self)?, false)); + let interval = Interval { + addr: Address::from_bytes(ptr.get(self)?)?, + size: from_bytes!(usize, len.get(self)?), + }; + match fd { + 1 => { + self.write_to_stdout(interval)?; + } + 2 => { + self.write_to_stderr(interval)?; + } + _ => not_supported!("write to arbitrary file descriptor"), + } + destination.write_from_interval(self, len.interval)?; + Ok(()) + } + "pthread_key_create" => { + let key = self.thread_local_storage.create_key(); + let Some(arg0) = args.get(0) else { + return Err(MirEvalError::TypeError("pthread_key_create arg0 is not provided")); + }; + let arg0_addr = Address::from_bytes(arg0.get(self)?)?; + let key_ty = if let Some((ty, ..)) = arg0.ty.as_reference_or_ptr() { + ty + } else { + return Err(MirEvalError::TypeError( + "pthread_key_create arg0 is not a pointer", + )); + }; + let arg0_interval = Interval::new( + arg0_addr, + self.size_of_sized(key_ty, locals, "pthread_key_create key arg")?, + ); + arg0_interval.write_from_bytes(self, &key.to_le_bytes()[0..arg0_interval.size])?; + // return 0 as success + destination.write_from_bytes(self, &0u64.to_le_bytes()[0..destination.size])?; + Ok(()) + } + "pthread_getspecific" => { + let Some(arg0) = args.get(0) else { + return Err(MirEvalError::TypeError("pthread_getspecific arg0 is not provided")); + }; + let key = from_bytes!(usize, &pad16(arg0.get(self)?, false)[0..8]); + let value = self.thread_local_storage.get_key(key)?; + destination.write_from_bytes(self, &value.to_le_bytes()[0..destination.size])?; + Ok(()) + } + "pthread_setspecific" => { + let Some(arg0) = args.get(0) else { + return Err(MirEvalError::TypeError("pthread_setspecific arg0 is not provided")); + }; + let key = from_bytes!(usize, &pad16(arg0.get(self)?, false)[0..8]); + let Some(arg1) = args.get(1) else { + return Err(MirEvalError::TypeError("pthread_setspecific arg1 is not provided")); + }; + let value = from_bytes!(u128, pad16(arg1.get(self)?, false)); + self.thread_local_storage.set_key(key, value)?; + // return 0 as success + destination.write_from_bytes(self, &0u64.to_le_bytes()[0..destination.size])?; + Ok(()) + } + "pthread_key_delete" => { + // we ignore this currently + // return 0 as success + destination.write_from_bytes(self, &0u64.to_le_bytes()[0..destination.size])?; + Ok(()) + } + _ => not_supported!("unknown external function {as_str}"), + } + } + + fn exec_intrinsic( + &mut self, + name: &str, + args: &[IntervalAndTy], + generic_args: &Substitution, + destination: Interval, + locals: &Locals<'_>, + span: MirSpan, + ) -> Result<()> { + if let Some(name) = name.strip_prefix("atomic_") { + return self.exec_atomic_intrinsic(name, args, generic_args, destination, locals, span); + } + if let Some(name) = name.strip_suffix("f64") { + let result = match name { + "sqrt" | "sin" | "cos" | "exp" | "exp2" | "log" | "log10" | "log2" | "fabs" + | "floor" | "ceil" | "trunc" | "rint" | "nearbyint" | "round" | "roundeven" => { + let [arg] = args else { + return Err(MirEvalError::TypeError("f64 intrinsic signature doesn't match fn (f64) -> f64")); + }; + let arg = from_bytes!(f64, arg.get(self)?); + match name { + "sqrt" => arg.sqrt(), + "sin" => arg.sin(), + "cos" => arg.cos(), + "exp" => arg.exp(), + "exp2" => arg.exp2(), + "log" => arg.ln(), + "log10" => arg.log10(), + "log2" => arg.log2(), + "fabs" => arg.abs(), + "floor" => arg.floor(), + "ceil" => arg.ceil(), + "trunc" => arg.trunc(), + // FIXME: these rounds should be different, but only `.round()` is stable now. + "rint" => arg.round(), + "nearbyint" => arg.round(), + "round" => arg.round(), + "roundeven" => arg.round(), + _ => unreachable!(), + } + } + "pow" | "minnum" | "maxnum" | "copysign" => { + let [arg1, arg2] = args else { + return Err(MirEvalError::TypeError("f64 intrinsic signature doesn't match fn (f64, f64) -> f64")); + }; + let arg1 = from_bytes!(f64, arg1.get(self)?); + let arg2 = from_bytes!(f64, arg2.get(self)?); + match name { + "pow" => arg1.powf(arg2), + "minnum" => arg1.min(arg2), + "maxnum" => arg1.max(arg2), + "copysign" => arg1.copysign(arg2), + _ => unreachable!(), + } + } + "powi" => { + let [arg1, arg2] = args else { + return Err(MirEvalError::TypeError("powif64 signature doesn't match fn (f64, i32) -> f64")); + }; + let arg1 = from_bytes!(f64, arg1.get(self)?); + let arg2 = from_bytes!(i32, arg2.get(self)?); + arg1.powi(arg2) + } + "fma" => { + let [arg1, arg2, arg3] = args else { + return Err(MirEvalError::TypeError("fmaf64 signature doesn't match fn (f64, f64, f64) -> f64")); + }; + let arg1 = from_bytes!(f64, arg1.get(self)?); + let arg2 = from_bytes!(f64, arg2.get(self)?); + let arg3 = from_bytes!(f64, arg3.get(self)?); + arg1.mul_add(arg2, arg3) + } + _ => not_supported!("unknown f64 intrinsic {name}"), + }; + return destination.write_from_bytes(self, &result.to_le_bytes()); + } + if let Some(name) = name.strip_suffix("f32") { + let result = match name { + "sqrt" | "sin" | "cos" | "exp" | "exp2" | "log" | "log10" | "log2" | "fabs" + | "floor" | "ceil" | "trunc" | "rint" | "nearbyint" | "round" | "roundeven" => { + let [arg] = args else { + return Err(MirEvalError::TypeError("f32 intrinsic signature doesn't match fn (f32) -> f32")); + }; + let arg = from_bytes!(f32, arg.get(self)?); + match name { + "sqrt" => arg.sqrt(), + "sin" => arg.sin(), + "cos" => arg.cos(), + "exp" => arg.exp(), + "exp2" => arg.exp2(), + "log" => arg.ln(), + "log10" => arg.log10(), + "log2" => arg.log2(), + "fabs" => arg.abs(), + "floor" => arg.floor(), + "ceil" => arg.ceil(), + "trunc" => arg.trunc(), + // FIXME: these rounds should be different, but only `.round()` is stable now. + "rint" => arg.round(), + "nearbyint" => arg.round(), + "round" => arg.round(), + "roundeven" => arg.round(), + _ => unreachable!(), + } + } + "pow" | "minnum" | "maxnum" | "copysign" => { + let [arg1, arg2] = args else { + return Err(MirEvalError::TypeError("f32 intrinsic signature doesn't match fn (f32, f32) -> f32")); + }; + let arg1 = from_bytes!(f32, arg1.get(self)?); + let arg2 = from_bytes!(f32, arg2.get(self)?); + match name { + "pow" => arg1.powf(arg2), + "minnum" => arg1.min(arg2), + "maxnum" => arg1.max(arg2), + "copysign" => arg1.copysign(arg2), + _ => unreachable!(), + } + } + "powi" => { + let [arg1, arg2] = args else { + return Err(MirEvalError::TypeError("powif32 signature doesn't match fn (f32, i32) -> f32")); + }; + let arg1 = from_bytes!(f32, arg1.get(self)?); + let arg2 = from_bytes!(i32, arg2.get(self)?); + arg1.powi(arg2) + } + "fma" => { + let [arg1, arg2, arg3] = args else { + return Err(MirEvalError::TypeError("fmaf32 signature doesn't match fn (f32, f32, f32) -> f32")); + }; + let arg1 = from_bytes!(f32, arg1.get(self)?); + let arg2 = from_bytes!(f32, arg2.get(self)?); + let arg3 = from_bytes!(f32, arg3.get(self)?); + arg1.mul_add(arg2, arg3) + } + _ => not_supported!("unknown f32 intrinsic {name}"), + }; + return destination.write_from_bytes(self, &result.to_le_bytes()); + } + match name { + "size_of" => { + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("size_of generic arg is not provided")); + }; + let size = self.size_of_sized(ty, locals, "size_of arg")?; + destination.write_from_bytes(self, &size.to_le_bytes()[0..destination.size]) + } + "min_align_of" | "pref_align_of" => { + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("align_of generic arg is not provided")); + }; + let align = self.layout(ty)?.align.abi.bytes(); + destination.write_from_bytes(self, &align.to_le_bytes()[0..destination.size]) + } + "needs_drop" => { + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("size_of generic arg is not provided")); + }; + let result = !ty.clone().is_copy(self.db, locals.body.owner); + destination.write_from_bytes(self, &[u8::from(result)]) + } + "ptr_guaranteed_cmp" => { + // FIXME: this is wrong for const eval, it should return 2 in some + // cases. + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("wrapping_add args are not provided")); + }; + let ans = lhs.get(self)? == rhs.get(self)?; + destination.write_from_bytes(self, &[u8::from(ans)]) + } + "saturating_add" => { + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("saturating_add args are not provided")); + }; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.saturating_add(rhs); + let bits = destination.size * 8; + // FIXME: signed + let is_signed = false; + let mx: u128 = if is_signed { (1 << (bits - 1)) - 1 } else { (1 << bits) - 1 }; + // FIXME: signed + let mn: u128 = 0; + let ans = cmp::min(mx, cmp::max(mn, ans)); + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "wrapping_add" | "unchecked_add" => { + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("wrapping_add args are not provided")); + }; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.wrapping_add(rhs); + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "wrapping_sub" | "unchecked_sub" | "ptr_offset_from_unsigned" | "ptr_offset_from" => { + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("wrapping_sub args are not provided")); + }; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.wrapping_sub(rhs); + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "wrapping_mul" | "unchecked_mul" => { + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("wrapping_mul args are not provided")); + }; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.wrapping_mul(rhs); + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "unchecked_rem" => { + // FIXME: signed + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("unchecked_rem args are not provided")); + }; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.checked_rem(rhs).ok_or_else(|| { + MirEvalError::UndefinedBehavior("unchecked_rem with bad inputs".to_owned()) + })?; + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "unchecked_div" | "exact_div" => { + // FIXME: signed + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("unchecked_div args are not provided")); + }; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.checked_div(rhs).ok_or_else(|| { + MirEvalError::UndefinedBehavior("unchecked_rem with bad inputs".to_owned()) + })?; + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "add_with_overflow" | "sub_with_overflow" | "mul_with_overflow" => { + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("const_eval_select args are not provided")); + }; + let result_ty = TyKind::Tuple( + 2, + Substitution::from_iter(Interner, [lhs.ty.clone(), TyBuilder::bool()]), + ) + .intern(Interner); + let op_size = + self.size_of_sized(&lhs.ty, locals, "operand of add_with_overflow")?; + let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false)); + let (ans, u128overflow) = match name { + "add_with_overflow" => lhs.overflowing_add(rhs), + "sub_with_overflow" => lhs.overflowing_sub(rhs), + "mul_with_overflow" => lhs.overflowing_mul(rhs), + _ => unreachable!(), + }; + let is_overflow = u128overflow + || ans.to_le_bytes()[op_size..].iter().any(|&x| x != 0 && x != 255); + let is_overflow = vec![u8::from(is_overflow)]; + let layout = self.layout(&result_ty)?; + let result = self.make_by_layout( + layout.size.bytes_usize(), + &layout, + None, + [ans.to_le_bytes()[0..op_size].to_vec(), is_overflow] + .into_iter() + .map(IntervalOrOwned::Owned), + )?; + destination.write_from_bytes(self, &result) + } + "copy" | "copy_nonoverlapping" => { + let [src, dst, offset] = args else { + return Err(MirEvalError::TypeError("copy_nonoverlapping args are not provided")); + }; + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("copy_nonoverlapping generic arg is not provided")); + }; + let src = Address::from_bytes(src.get(self)?)?; + let dst = Address::from_bytes(dst.get(self)?)?; + let offset = from_bytes!(usize, offset.get(self)?); + let size = self.size_of_sized(ty, locals, "copy_nonoverlapping ptr type")?; + let size = offset * size; + let src = Interval { addr: src, size }; + let dst = Interval { addr: dst, size }; + dst.write_from_interval(self, src) + } + "offset" | "arith_offset" => { + let [ptr, offset] = args else { + return Err(MirEvalError::TypeError("offset args are not provided")); + }; + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("offset generic arg is not provided")); + }; + let ptr = u128::from_le_bytes(pad16(ptr.get(self)?, false)); + let offset = u128::from_le_bytes(pad16(offset.get(self)?, false)); + let size = self.size_of_sized(ty, locals, "offset ptr type")? as u128; + let ans = ptr + offset * size; + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "assert_inhabited" | "assert_zero_valid" | "assert_uninit_valid" | "assume" => { + // FIXME: We should actually implement these checks + Ok(()) + } + "forget" => { + // We don't call any drop glue yet, so there is nothing here + Ok(()) + } + "transmute" => { + let [arg] = args else { + return Err(MirEvalError::TypeError("trasmute arg is not provided")); + }; + destination.write_from_interval(self, arg.interval) + } + "likely" | "unlikely" => { + let [arg] = args else { + return Err(MirEvalError::TypeError("likely arg is not provided")); + }; + destination.write_from_interval(self, arg.interval) + } + "ctpop" => { + let [arg] = args else { + return Err(MirEvalError::TypeError("likely arg is not provided")); + }; + let result = u128::from_le_bytes(pad16(arg.get(self)?, false)).count_ones(); + destination + .write_from_bytes(self, &(result as u128).to_le_bytes()[0..destination.size]) + } + "cttz" | "cttz_nonzero" => { + let [arg] = args else { + return Err(MirEvalError::TypeError("likely arg is not provided")); + }; + let result = u128::from_le_bytes(pad16(arg.get(self)?, false)).trailing_zeros(); + destination + .write_from_bytes(self, &(result as u128).to_le_bytes()[0..destination.size]) + } + "const_eval_select" => { + let [tuple, const_fn, _] = args else { + return Err(MirEvalError::TypeError("const_eval_select args are not provided")); + }; + let mut args = vec![const_fn.clone()]; + let TyKind::Tuple(_, fields) = tuple.ty.kind(Interner) else { + return Err(MirEvalError::TypeError("const_eval_select arg[0] is not a tuple")); + }; + let layout = self.layout(&tuple.ty)?; + for (i, field) in fields.iter(Interner).enumerate() { + let field = field.assert_ty_ref(Interner).clone(); + let offset = layout.fields.offset(i).bytes_usize(); + let addr = tuple.interval.addr.offset(offset); + args.push(IntervalAndTy::new(addr, field, self, locals)?); + } + self.exec_fn_trait(&args, destination, locals, span) + } + _ => not_supported!("unknown intrinsic {name}"), + } + } + + fn exec_atomic_intrinsic( + &mut self, + name: &str, + args: &[IntervalAndTy], + generic_args: &Substitution, + destination: Interval, + locals: &Locals<'_>, + _span: MirSpan, + ) -> Result<()> { + // We are a single threaded runtime with no UB checking and no optimization, so + // we can implement these as normal functions. + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("atomic intrinsic generic arg is not provided")); + }; + let Some(arg0) = args.get(0) else { + return Err(MirEvalError::TypeError("atomic intrinsic arg0 is not provided")); + }; + let arg0_addr = Address::from_bytes(arg0.get(self)?)?; + let arg0_interval = + Interval::new(arg0_addr, self.size_of_sized(ty, locals, "atomic intrinsic type arg")?); + if name.starts_with("load_") { + return destination.write_from_interval(self, arg0_interval); + } + let Some(arg1) = args.get(1) else { + return Err(MirEvalError::TypeError("atomic intrinsic arg1 is not provided")); + }; + if name.starts_with("store_") { + return arg0_interval.write_from_interval(self, arg1.interval); + } + if name.starts_with("xchg_") { + destination.write_from_interval(self, arg0_interval)?; + return arg0_interval.write_from_interval(self, arg1.interval); + } + if name.starts_with("xadd_") { + destination.write_from_interval(self, arg0_interval)?; + let lhs = u128::from_le_bytes(pad16(arg0_interval.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(arg1.get(self)?, false)); + let ans = lhs.wrapping_add(rhs); + return arg0_interval.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]); + } + if name.starts_with("xsub_") { + destination.write_from_interval(self, arg0_interval)?; + let lhs = u128::from_le_bytes(pad16(arg0_interval.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(arg1.get(self)?, false)); + let ans = lhs.wrapping_sub(rhs); + return arg0_interval.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]); + } + if name.starts_with("and_") { + destination.write_from_interval(self, arg0_interval)?; + let lhs = u128::from_le_bytes(pad16(arg0_interval.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(arg1.get(self)?, false)); + let ans = lhs & rhs; + return arg0_interval.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]); + } + if name.starts_with("or_") { + destination.write_from_interval(self, arg0_interval)?; + let lhs = u128::from_le_bytes(pad16(arg0_interval.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(arg1.get(self)?, false)); + let ans = lhs | rhs; + return arg0_interval.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]); + } + if name.starts_with("xor_") { + destination.write_from_interval(self, arg0_interval)?; + let lhs = u128::from_le_bytes(pad16(arg0_interval.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(arg1.get(self)?, false)); + let ans = lhs ^ rhs; + return arg0_interval.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]); + } + if name.starts_with("nand_") { + destination.write_from_interval(self, arg0_interval)?; + let lhs = u128::from_le_bytes(pad16(arg0_interval.get(self)?, false)); + let rhs = u128::from_le_bytes(pad16(arg1.get(self)?, false)); + let ans = !(lhs & rhs); + return arg0_interval.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]); + } + let Some(arg2) = args.get(2) else { + return Err(MirEvalError::TypeError("atomic intrinsic arg2 is not provided")); + }; + if name.starts_with("cxchg_") || name.starts_with("cxchgweak_") { + let dest = if arg1.get(self)? == arg0_interval.get(self)? { + arg0_interval.write_from_interval(self, arg2.interval)?; + (arg1.interval, true) + } else { + (arg0_interval, false) + }; + let result_ty = TyKind::Tuple( + 2, + Substitution::from_iter(Interner, [ty.clone(), TyBuilder::bool()]), + ) + .intern(Interner); + let layout = self.layout(&result_ty)?; + let result = self.make_by_layout( + layout.size.bytes_usize(), + &layout, + None, + [IntervalOrOwned::Borrowed(dest.0), IntervalOrOwned::Owned(vec![u8::from(dest.1)])] + .into_iter(), + )?; + return destination.write_from_bytes(self, &result); + } + not_supported!("unknown atomic intrinsic {name}"); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs new file mode 100644 index 000000000000..ca4268b8fb00 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs @@ -0,0 +1,676 @@ +use base_db::{fixture::WithFixture, FileId}; +use hir_def::db::DefDatabase; +use syntax::{TextRange, TextSize}; + +use crate::{db::HirDatabase, test_db::TestDB, Interner, Substitution}; + +use super::{interpret_mir, MirEvalError}; + +fn eval_main(db: &TestDB, file_id: FileId) -> Result<(String, String), MirEvalError> { + let module_id = db.module_for_file(file_id); + let def_map = module_id.def_map(db); + let scope = &def_map[module_id.local_id].scope; + let func_id = scope + .declarations() + .find_map(|x| match x { + hir_def::ModuleDefId::FunctionId(x) => { + if db.function_data(x).name.display(db).to_string() == "main" { + Some(x) + } else { + None + } + } + _ => None, + }) + .expect("no main function found"); + let body = db + .monomorphized_mir_body( + func_id.into(), + Substitution::empty(Interner), + db.trait_environment(func_id.into()), + ) + .map_err(|e| MirEvalError::MirLowerError(func_id.into(), e))?; + let (result, stdout, stderr) = interpret_mir(db, &body, false); + result?; + Ok((stdout, stderr)) +} + +fn check_pass(ra_fixture: &str) { + check_pass_and_stdio(ra_fixture, "", ""); +} + +fn check_pass_and_stdio(ra_fixture: &str, expected_stdout: &str, expected_stderr: &str) { + let (db, file_ids) = TestDB::with_many_files(ra_fixture); + let file_id = *file_ids.last().unwrap(); + let x = eval_main(&db, file_id); + match x { + Err(e) => { + let mut err = String::new(); + let line_index = |size: TextSize| { + let mut size = u32::from(size) as usize; + let mut lines = ra_fixture.lines().enumerate(); + while let Some((i, l)) = lines.next() { + if let Some(x) = size.checked_sub(l.len()) { + size = x; + } else { + return (i, size); + } + } + (usize::MAX, size) + }; + let span_formatter = |file, range: TextRange| { + format!("{:?} {:?}..{:?}", file, line_index(range.start()), line_index(range.end())) + }; + e.pretty_print(&mut err, &db, span_formatter).unwrap(); + panic!("Error in interpreting: {err}"); + } + Ok((stdout, stderr)) => { + assert_eq!(stdout, expected_stdout); + assert_eq!(stderr, expected_stderr); + } + } +} + +#[test] +fn function_with_extern_c_abi() { + check_pass( + r#" +extern "C" fn foo(a: i32, b: i32) -> i32 { + a + b +} + +fn main() { + let x = foo(2, 3); +} + "#, + ); +} + +#[test] +fn drop_basic() { + check_pass( + r#" +//- minicore: drop, add + +struct X<'a>(&'a mut i32); +impl<'a> Drop for X<'a> { + fn drop(&mut self) { + *self.0 += 1; + } +} + +struct NestedX<'a> { f1: X<'a>, f2: X<'a> } + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn my_drop2(x: X<'_>) { + return; +} + +fn my_drop(x: X<'_>) { + drop(x); +} + +fn main() { + let mut s = 10; + let mut x = X(&mut s); + my_drop(x); + x = X(&mut s); + my_drop2(x); + X(&mut s); // dropped immediately + let x = X(&mut s); + NestedX { f1: x, f2: X(&mut s) }; + if s != 15 { + should_not_reach(); + } +} + "#, + ); +} + +#[test] +fn drop_if_let() { + check_pass( + r#" +//- minicore: drop, add, option, cell, builtin_impls + +use core::cell::Cell; + +struct X<'a>(&'a Cell); +impl<'a> Drop for X<'a> { + fn drop(&mut self) { + self.0.set(self.0.get() + 1) + } +} + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +#[test] +fn main() { + let s = Cell::new(0); + let x = Some(X(&s)); + if let Some(y) = x { + if s.get() != 0 { + should_not_reach(); + } + if s.get() != 0 { + should_not_reach(); + } + } else { + should_not_reach(); + } + if s.get() != 1 { + should_not_reach(); + } + let x = Some(X(&s)); + if let None = x { + should_not_reach(); + } else { + if s.get() != 1 { + should_not_reach(); + } + } + if s.get() != 1 { + should_not_reach(); + } +} + "#, + ); +} + +#[test] +fn drop_in_place() { + check_pass( + r#" +//- minicore: drop, add, coerce_unsized +use core::ptr::drop_in_place; + +struct X<'a>(&'a mut i32); +impl<'a> Drop for X<'a> { + fn drop(&mut self) { + *self.0 += 1; + } +} + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + let mut s = 2; + let x = X(&mut s); + drop_in_place(&mut x); + drop(x); + if s != 4 { + should_not_reach(); + } + let p: &mut [X] = &mut [X(&mut 2)]; + drop_in_place(p); +} + "#, + ); +} + +#[test] +fn manually_drop() { + check_pass( + r#" +//- minicore: manually_drop +use core::mem::ManuallyDrop; + +struct X; +impl Drop for X { + fn drop(&mut self) { + should_not_reach(); + } +} + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + let x = ManuallyDrop::new(X); +} + "#, + ); +} + +#[test] +fn generic_impl_for_trait_with_generic_method() { + check_pass( + r#" +//- minicore: drop +struct S(T); + +trait Tr { + fn f(&self, x: F); +} + +impl Tr for S { + fn f(&self, x: F) { + } +} + +fn main() { + let s = S(1u8); + s.f(5i64); +} + "#, + ); +} + +#[test] +fn index_of_slice_should_preserve_len() { + check_pass( + r#" +//- minicore: index, slice, coerce_unsized + +struct X; + +impl core::ops::Index for [i32] { + type Output = i32; + + fn index(&self, _: X) -> &i32 { + if self.len() != 3 { + should_not_reach(); + } + &self[0] + } +} + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + let x: &[i32] = &[1, 2, 3]; + &x[X]; +} + "#, + ); +} + +#[test] +fn memcmp() { + check_pass( + r#" +//- minicore: slice, coerce_unsized, index + +fn should_not_reach() -> bool { + _ // FIXME: replace this function with panic when that works +} + +extern "C" { + fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32; +} + +fn my_cmp(x: &[u8], y: &[u8]) -> i32 { + memcmp(x as *const u8, y as *const u8, x.len()) +} + +fn main() { + if my_cmp(&[1, 2, 3], &[1, 2, 3]) != 0 { + should_not_reach(); + } + if my_cmp(&[1, 20, 3], &[1, 2, 3]) <= 0 { + should_not_reach(); + } + if my_cmp(&[1, 2, 3], &[1, 20, 3]) >= 0 { + should_not_reach(); + } +} + "#, + ); +} + +#[test] +fn unix_write_stdout() { + check_pass_and_stdio( + r#" +//- minicore: slice, index, coerce_unsized + +type pthread_key_t = u32; +type c_void = u8; +type c_int = i32; + +extern "C" { + pub fn write(fd: i32, buf: *const u8, count: usize) -> usize; +} + +fn main() { + let stdout = b"stdout"; + let stderr = b"stderr"; + write(1, &stdout[0], 6); + write(2, &stderr[0], 6); +} + "#, + "stdout", + "stderr", + ); +} + +#[test] +fn closure_layout_in_rpit() { + check_pass( + r#" +//- minicore: fn + +fn f(x: F) { + fn g(x: impl Fn()) -> impl FnOnce() { + move || { + x(); + } + } + g(x)(); +} + +fn main() { + f(|| {}); +} + "#, + ); +} + +#[test] +fn from_fn() { + check_pass( + r#" +//- minicore: fn, iterator +struct FromFn(F); + +impl Option> Iterator for FromFn { + type Item = T; + + fn next(&mut self) -> Option { + (self.0)() + } +} + +fn main() { + let mut tokenize = { + FromFn(move || Some(2)) + }; + let s = tokenize.next(); +} + "#, + ); +} + +#[test] +fn for_loop() { + check_pass( + r#" +//- minicore: iterator, add +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +struct X; +struct XIter(i32); + +impl IntoIterator for X { + type Item = i32; + + type IntoIter = XIter; + + fn into_iter(self) -> Self::IntoIter { + XIter(0) + } +} + +impl Iterator for XIter { + type Item = i32; + + fn next(&mut self) -> Option { + if self.0 == 5 { + None + } else { + self.0 += 1; + Some(self.0) + } + } +} + +fn main() { + let mut s = 0; + for x in X { + s += x; + } + if s != 15 { + should_not_reach(); + } +} + "#, + ); +} + +#[test] +fn field_with_associated_type() { + check_pass( + r#" +//- /b/mod.rs crate:b +pub trait Tr { + fn f(self); +} + +pub trait Tr2 { + type Ty: Tr; +} + +pub struct S { + pub t: T::Ty, +} + +impl S { + pub fn g(&self) { + let k = (self.t, self.t); + self.t.f(); + } +} + +//- /a/mod.rs crate:a deps:b +use b::{Tr, Tr2, S}; + +struct A(i32); +struct B(u8); + +impl Tr for A { + fn f(&self) { + } +} + +impl Tr2 for B { + type Ty = A; +} + +#[test] +fn main() { + let s: S = S { t: A(2) }; + s.g(); +} + "#, + ); +} + +#[test] +fn specialization_array_clone() { + check_pass( + r#" +//- minicore: copy, derive, slice, index, coerce_unsized +impl Clone for [T; N] { + #[inline] + fn clone(&self) -> Self { + SpecArrayClone::clone(self) + } +} + +trait SpecArrayClone: Clone { + fn clone(array: &[Self; N]) -> [Self; N]; +} + +impl SpecArrayClone for T { + #[inline] + default fn clone(array: &[T; N]) -> [T; N] { + // FIXME: panic here when we actually implement specialization. + from_slice(array) + } +} + +fn from_slice(s: &[T]) -> [T; N] { + [s[0]; N] +} + +impl SpecArrayClone for T { + #[inline] + fn clone(array: &[T; N]) -> [T; N] { + *array + } +} + +#[derive(Clone, Copy)] +struct X(i32); + +fn main() { + let ar = [X(1), X(2)]; + ar.clone(); +} + "#, + ); +} + +#[test] +fn short_circuit_operator() { + check_pass( + r#" +fn should_not_reach() -> bool { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + if false && should_not_reach() { + should_not_reach(); + } + true || should_not_reach(); + +} + "#, + ); +} + +#[test] +fn closure_state() { + check_pass( + r#" +//- minicore: fn, add, copy +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + let mut x = 2; + let mut c = move || { + x += 1; + x + }; + c(); + c(); + c(); + if x != 2 { + should_not_reach(); + } + if c() != 6 { + should_not_reach(); + } +} + "#, + ); +} + +#[test] +fn closure_capture_array_const_generic() { + check_pass( + r#" +//- minicore: fn, add, copy +struct X(i32); + +fn f(mut x: [X; N]) { // -> impl FnOnce() { + let c = || { + x; + }; + c(); +} + +fn main() { + let s = f([X(1)]); + //s(); +} + "#, + ); +} + +#[test] +fn posix_tls() { + check_pass( + r#" +//- minicore: option + +type pthread_key_t = u32; +type c_void = u8; +type c_int = i32; + +extern "C" { + pub fn pthread_key_create( + key: *mut pthread_key_t, + dtor: Option, + ) -> c_int; + pub fn pthread_key_delete(key: pthread_key_t) -> c_int; + pub fn pthread_getspecific(key: pthread_key_t) -> *mut c_void; + pub fn pthread_setspecific(key: pthread_key_t, value: *const c_void) -> c_int; +} + +fn main() { + let mut key = 2; + pthread_key_create(&mut key, None); +} + "#, + ); +} + +#[test] +fn regression_14966() { + check_pass( + r#" +//- minicore: fn, copy, coerce_unsized +trait A { + fn a(&self) {} +} +impl A<()> for () {} + +struct B; +impl B { + pub fn b(s: &dyn A) -> Self { + B + } +} +struct C; +impl C { + fn c(a: &dyn A) -> Self { + let mut c = C; + let b = B::b(a); + c.d(|| a.a()); + c + } + fn d(&mut self, f: impl FnOnce()) {} +} + +fn main() { + C::c(&()); +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index c4dd7c0ace46..aad1a82f2981 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -1,60 +1,88 @@ //! This module generates a polymorphic MIR from a hir body -use std::{iter, mem, sync::Arc}; +use std::{fmt::Write, iter, mem}; +use base_db::FileId; use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; use hir_def::{ body::Body, - expr::{ - Array, BindingAnnotation, BindingId, ExprId, LabelId, Literal, MatchArm, Pat, PatId, - RecordLitField, + data::adt::{StructKind, VariantData}, + hir::{ + ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal, + LiteralOrConst, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField, }, lang_item::{LangItem, LangItemTarget}, - layout::LayoutError, path::Path, - resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, - DefWithBodyId, EnumVariantId, HasModule, + resolver::{resolver_for_expr, HasResolver, ResolveValueResult, ValueNs}, + AdtId, DefWithBodyId, EnumVariantId, GeneralConstId, HasModule, ItemContainerId, LocalFieldId, + TraitId, TypeOrConstParamId, }; use hir_expand::name::Name; use la_arena::ArenaMap; +use rustc_hash::FxHashMap; +use syntax::TextRange; +use triomphe::Arc; use crate::{ - consteval::ConstEvalError, db::HirDatabase, display::HirDisplay, infer::TypeMismatch, - inhabitedness::is_ty_uninhabited_from, layout::layout_of_ty, mapping::ToChalk, static_lifetime, - utils::generics, Adjust, Adjustment, AutoBorrow, CallableDefId, TyBuilder, TyExt, + consteval::ConstEvalError, + db::HirDatabase, + display::HirDisplay, + infer::{CaptureKind, CapturedItem, TypeMismatch}, + inhabitedness::is_ty_uninhabited_from, + layout::LayoutError, + mapping::ToChalk, + static_lifetime, + traits::FnTrait, + utils::{generics, ClosureSubst}, + Adjust, Adjustment, AutoBorrow, CallableDefId, TyBuilder, TyExt, }; use super::*; mod as_place; +mod pattern_matching; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct LoopBlocks { begin: BasicBlockId, /// `None` for loops that are not terminating end: Option, + place: Place, + drop_scope_index: usize, +} + +#[derive(Debug, Clone, Default)] +struct DropScope { + /// locals, in order of definition (so we should run drop glues in reverse order) + locals: Vec, } struct MirLowerCtx<'a> { result: MirBody, owner: DefWithBodyId, current_loop_blocks: Option, + labeled_loop_blocks: FxHashMap, discr_temp: Option, db: &'a dyn HirDatabase, body: &'a Body, infer: &'a InferenceResult, + drop_scopes: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum MirLowerError { - ConstEvalError(Box), + ConstEvalError(String, Box), LayoutError(LayoutError), IncompleteExpr, + IncompletePattern, + /// Trying to lower a trait function, instead of an implementation + TraitFunctionDefinition(TraitId, Name), UnresolvedName(String), RecordLiteralWithoutPath, - UnresolvedMethod, + UnresolvedMethod(String), UnresolvedField, - MissingFunctionDefinition, + UnsizedTemporary(Ty), + MissingFunctionDefinition(DefWithBodyId, ExprId), TypeMismatch(TypeMismatch), /// This should be never happen. Type mismatch should catch everything. TypeError(&'static str), @@ -63,9 +91,114 @@ pub enum MirLowerError { BreakWithoutLoop, Loop, /// Something that should never happen and is definitely a bug, but we don't want to panic if it happened - ImplementationError(&'static str), + ImplementationError(String), LangItemNotFound(LangItem), MutatingRvalue, + UnresolvedLabel, + UnresolvedUpvar(Place), + UnaccessableLocal, + + // monomorphization errors: + GenericArgNotProvided(TypeOrConstParamId, Substitution), +} + +/// A token to ensuring that each drop scope is popped at most once, thanks to the compiler that checks moves. +struct DropScopeToken; +impl DropScopeToken { + fn pop_and_drop(self, ctx: &mut MirLowerCtx<'_>, current: BasicBlockId) -> BasicBlockId { + std::mem::forget(self); + ctx.pop_drop_scope_internal(current) + } + + /// It is useful when we want a drop scope is syntaxically closed, but we don't want to execute any drop + /// code. Either when the control flow is diverging (so drop code doesn't reached) or when drop is handled + /// for us (for example a block that ended with a return statement. Return will drop everything, so the block shouldn't + /// do anything) + fn pop_assume_dropped(self, ctx: &mut MirLowerCtx<'_>) { + std::mem::forget(self); + ctx.pop_drop_scope_assume_dropped_internal(); + } +} + +// Uncomment this to make `DropScopeToken` a drop bomb. Unfortunately we can't do this in release, since +// in cases that mir lowering fails, we don't handle (and don't need to handle) drop scopes so it will be +// actually reached. `pop_drop_scope_assert_finished` will also detect this case, but doesn't show useful +// stack trace. +// +// impl Drop for DropScopeToken { +// fn drop(&mut self) { +// never!("Drop scope doesn't popped"); +// } +// } + +impl MirLowerError { + pub fn pretty_print( + &self, + f: &mut String, + db: &dyn HirDatabase, + span_formatter: impl Fn(FileId, TextRange) -> String, + ) -> std::result::Result<(), std::fmt::Error> { + match self { + MirLowerError::ConstEvalError(name, e) => { + writeln!(f, "In evaluating constant {name}")?; + match &**e { + ConstEvalError::MirLowerError(e) => e.pretty_print(f, db, span_formatter)?, + ConstEvalError::MirEvalError(e) => e.pretty_print(f, db, span_formatter)?, + } + } + MirLowerError::MissingFunctionDefinition(owner, x) => { + let body = db.body(*owner); + writeln!( + f, + "Missing function definition for {}", + body.pretty_print_expr(db.upcast(), *owner, *x) + )?; + } + MirLowerError::TypeMismatch(e) => { + writeln!( + f, + "Type mismatch: Expected {}, found {}", + e.expected.display(db), + e.actual.display(db), + )?; + } + MirLowerError::GenericArgNotProvided(id, subst) => { + let parent = id.parent; + let param = &db.generic_params(parent).type_or_consts[id.local_id]; + writeln!( + f, + "Generic arg not provided for {}", + param.name().unwrap_or(&Name::missing()).display(db.upcast()) + )?; + writeln!(f, "Provided args: [")?; + for g in subst.iter(Interner) { + write!(f, " {},", g.display(db).to_string())?; + } + writeln!(f, "]")?; + } + MirLowerError::LayoutError(_) + | MirLowerError::UnsizedTemporary(_) + | MirLowerError::IncompleteExpr + | MirLowerError::IncompletePattern + | MirLowerError::UnaccessableLocal + | MirLowerError::TraitFunctionDefinition(_, _) + | MirLowerError::UnresolvedName(_) + | MirLowerError::RecordLiteralWithoutPath + | MirLowerError::UnresolvedMethod(_) + | MirLowerError::UnresolvedField + | MirLowerError::TypeError(_) + | MirLowerError::NotSupported(_) + | MirLowerError::ContinueWithoutLoop + | MirLowerError::BreakWithoutLoop + | MirLowerError::Loop + | MirLowerError::ImplementationError(_) + | MirLowerError::LangItemNotFound(_) + | MirLowerError::MutatingRvalue + | MirLowerError::UnresolvedLabel + | MirLowerError::UnresolvedUpvar(_) => writeln!(f, "{:?}", self)?, + } + Ok(()) + } } macro_rules! not_supported { @@ -76,20 +209,11 @@ macro_rules! not_supported { macro_rules! implementation_error { ($x: expr) => {{ - ::stdx::never!("MIR lower implementation bug: {}", $x); - return Err(MirLowerError::ImplementationError($x)); + ::stdx::never!("MIR lower implementation bug: {}", format!($x)); + return Err(MirLowerError::ImplementationError(format!($x))); }}; } -impl From for MirLowerError { - fn from(value: ConstEvalError) -> Self { - match value { - ConstEvalError::MirLowerError(e) => e, - _ => MirLowerError::ConstEvalError(Box::new(value)), - } - } -} - impl From for MirLowerError { fn from(value: LayoutError) -> Self { MirLowerError::LayoutError(value) @@ -104,12 +228,51 @@ fn unresolved_path(db: &dyn HirDatabase, p: &Path) -> Self { type Result = std::result::Result; -impl MirLowerCtx<'_> { - fn temp(&mut self, ty: Ty) -> Result { +impl<'ctx> MirLowerCtx<'ctx> { + fn new( + db: &'ctx dyn HirDatabase, + owner: DefWithBodyId, + body: &'ctx Body, + infer: &'ctx InferenceResult, + ) -> Self { + let mut basic_blocks = Arena::new(); + let start_block = basic_blocks.alloc(BasicBlock { + statements: vec![], + terminator: None, + is_cleanup: false, + }); + let locals = Arena::new(); + let binding_locals: ArenaMap = ArenaMap::new(); + let mir = MirBody { + basic_blocks, + locals, + start_block, + binding_locals, + param_locals: vec![], + owner, + closures: vec![], + }; + let ctx = MirLowerCtx { + result: mir, + db, + infer, + body, + owner, + current_loop_blocks: None, + labeled_loop_blocks: Default::default(), + discr_temp: None, + drop_scopes: vec![DropScope::default()], + }; + ctx + } + + fn temp(&mut self, ty: Ty, current: BasicBlockId, span: MirSpan) -> Result { if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) { - implementation_error!("unsized temporaries"); + return Err(MirLowerError::UnsizedTemporary(ty)); } - Ok(self.result.locals.alloc(Local { ty })) + let l = self.result.locals.alloc(Local { ty }); + self.push_storage_live_for_local(l, current, span)?; + Ok(l) } fn lower_expr_to_some_operand( @@ -120,7 +283,7 @@ fn lower_expr_to_some_operand( if !self.has_adjustments(expr_id) { match &self.body.exprs[expr_id] { Expr::Literal(l) => { - let ty = self.expr_ty(expr_id); + let ty = self.expr_ty_without_adjust(expr_id); return Ok(Some((self.lower_literal_to_operand(ty, l)?, current))); } _ => (), @@ -142,7 +305,8 @@ fn lower_expr_to_place_with_adjust( match adjustments.split_last() { Some((last, rest)) => match &last.kind { Adjust::NeverToAny => { - let temp = self.temp(TyKind::Never.intern(Interner))?; + let temp = + self.temp(TyKind::Never.intern(Interner), current, MirSpan::Unknown)?; self.lower_expr_to_place_with_adjust(expr_id, temp.into(), current, rest) } Adjust::Deref(_) => { @@ -200,65 +364,82 @@ fn lower_expr_to_place_without_adjust( mut current: BasicBlockId, ) -> Result> { match &self.body.exprs[expr_id] { - Expr::Missing => Err(MirLowerError::IncompleteExpr), - Expr::Path(p) => { - let unresolved_name = || MirLowerError::unresolved_path(self.db, p); - let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); - let pr = resolver - .resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) - .ok_or_else(unresolved_name)?; - let pr = match pr { - ResolveValueResult::ValueNs(v) => v, - ResolveValueResult::Partial(..) => { - if let Some(assoc) = self - .infer - .assoc_resolutions_for_expr(expr_id) - { - match assoc.0 { - hir_def::AssocItemId::ConstId(c) => { - self.lower_const(c, current, place, expr_id.into())?; - return Ok(Some(current)) - }, - _ => not_supported!("associated functions and types"), - } - } else if let Some(variant) = self - .infer - .variant_resolution_for_expr(expr_id) - { - match variant { - VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e), - VariantId::StructId(s) => ValueNs::StructId(s), - VariantId::UnionId(_) => implementation_error!("Union variant as path"), - } - } else { - return Err(unresolved_name()); - } + Expr::Missing => { + if let DefWithBodyId::FunctionId(f) = self.owner { + let assoc = self.db.lookup_intern_function(f); + if let ItemContainerId::TraitId(t) = assoc.container { + let name = &self.db.function_data(f).name; + return Err(MirLowerError::TraitFunctionDefinition(t, name.clone())); } + } + Err(MirLowerError::IncompleteExpr) + }, + Expr::Path(p) => { + let pr = if let Some((assoc, subst)) = self + .infer + .assoc_resolutions_for_expr(expr_id) + { + match assoc { + hir_def::AssocItemId::ConstId(c) => { + self.lower_const(c.into(), current, place, subst, expr_id.into(), self.expr_ty_without_adjust(expr_id))?; + return Ok(Some(current)) + }, + hir_def::AssocItemId::FunctionId(_) => { + // FnDefs are zero sized, no action is needed. + return Ok(Some(current)) + } + hir_def::AssocItemId::TypeAliasId(_) => { + // FIXME: If it is unreachable, use proper error instead of `not_supported`. + not_supported!("associated functions and types") + }, + } + } else if let Some(variant) = self + .infer + .variant_resolution_for_expr(expr_id) + { + match variant { + VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e), + VariantId::StructId(s) => ValueNs::StructId(s), + VariantId::UnionId(_) => implementation_error!("Union variant as path"), + } + } else { + let unresolved_name = || MirLowerError::unresolved_path(self.db, p); + let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); + resolver + .resolve_path_in_value_ns_fully(self.db.upcast(), p) + .ok_or_else(unresolved_name)? }; match pr { - ValueNs::LocalBinding(pat_id) => { + ValueNs::LocalBinding(_) | ValueNs::StaticId(_) => { + let Some((temp, current)) = self.lower_expr_as_place_without_adjust(current, expr_id, false)? else { + return Ok(None); + }; self.push_assignment( current, place, - Operand::Copy(self.result.binding_locals[pat_id].into()).into(), + Operand::Copy(temp).into(), expr_id.into(), ); Ok(Some(current)) } ValueNs::ConstId(const_id) => { - self.lower_const(const_id, current, place, expr_id.into())?; + self.lower_const(const_id.into(), current, place, Substitution::empty(Interner), expr_id.into(), self.expr_ty_without_adjust(expr_id))?; Ok(Some(current)) } ValueNs::EnumVariantId(variant_id) => { - let ty = self.infer.type_of_expr[expr_id].clone(); - let current = self.lower_enum_variant( - variant_id, - current, - place, - ty, - vec![], - expr_id.into(), - )?; + let variant_data = &self.db.enum_data(variant_id.parent).variants[variant_id.local_id]; + if variant_data.variant_data.kind() == StructKind::Unit { + let ty = self.infer.type_of_expr[expr_id].clone(); + current = self.lower_enum_variant( + variant_id, + current, + place, + ty, + Box::new([]), + expr_id.into(), + )?; + } + // Otherwise its a tuple like enum, treated like a zero sized function, so no action is needed Ok(Some(current)) } ValueNs::GenericParam(p) => { @@ -266,7 +447,7 @@ fn lower_expr_to_place_without_adjust( not_supported!("owner without generic def id"); }; let gen = generics(self.db.upcast(), def); - let ty = self.expr_ty(expr_id); + let ty = self.expr_ty_without_adjust(expr_id); self.push_assignment( current, place, @@ -287,7 +468,7 @@ fn lower_expr_to_place_without_adjust( ); Ok(Some(current)) } - ValueNs::StructId(_) => { + ValueNs::FunctionId(_) | ValueNs::StructId(_) => { // It's probably a unit struct or a zero sized function, so no action is needed. Ok(Some(current)) } @@ -311,12 +492,13 @@ fn lower_expr_to_place_without_adjust( }; self.set_terminator( current, - Terminator::SwitchInt { + TerminatorKind::SwitchInt { discr, targets: SwitchTargets::static_if(1, start_of_then, start_of_else), }, + expr_id.into(), ); - Ok(self.merge_blocks(end_of_then, end_of_else)) + Ok(self.merge_blocks(end_of_then, end_of_else, expr_id.into())) } Expr::Let { pat, expr } => { let Some((cond_place, current)) = self.lower_expr_as_place(current, *expr, true)? else { @@ -326,9 +508,7 @@ fn lower_expr_to_place_without_adjust( current, None, cond_place, - self.expr_ty_after_adjustments(*expr), *pat, - BindingAnnotation::Unannotated, )?; self.write_bytes_to_place( then_target, @@ -346,141 +526,107 @@ fn lower_expr_to_place_without_adjust( MirSpan::Unknown, )?; } - Ok(self.merge_blocks(Some(then_target), else_target)) + Ok(self.merge_blocks(Some(then_target), else_target, expr_id.into())) } Expr::Unsafe { id: _, statements, tail } => { - self.lower_block_to_place(None, statements, current, *tail, place) + self.lower_block_to_place(statements, current, *tail, place, expr_id.into()) } Expr::Block { id: _, statements, tail, label } => { - self.lower_block_to_place(*label, statements, current, *tail, place) + if let Some(label) = label { + self.lower_loop(current, place.clone(), Some(*label), expr_id.into(), |this, begin| { + if let Some(current) = this.lower_block_to_place(statements, begin, *tail, place, expr_id.into())? { + let end = this.current_loop_end()?; + this.set_goto(current, end, expr_id.into()); + } + Ok(()) + }) + } else { + self.lower_block_to_place(statements, current, *tail, place, expr_id.into()) + } } - Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin| { - if let Some((_, block)) = this.lower_expr_as_place(begin, *body, true)? { - this.set_goto(block, begin); + Expr::Loop { body, label } => self.lower_loop(current, place, *label, expr_id.into(), |this, begin| { + let scope = this.push_drop_scope(); + if let Some((_, mut current)) = this.lower_expr_as_place(begin, *body, true)? { + current = scope.pop_and_drop(this, current); + this.set_goto(current, begin, expr_id.into()); + } else { + scope.pop_assume_dropped(this); } Ok(()) }), Expr::While { condition, body, label } => { - self.lower_loop(current, *label, |this, begin| { + self.lower_loop(current, place, *label, expr_id.into(),|this, begin| { + let scope = this.push_drop_scope(); let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else { return Ok(()); }; - let end = this.current_loop_end()?; + let fail_cond = this.new_basic_block(); let after_cond = this.new_basic_block(); this.set_terminator( to_switch, - Terminator::SwitchInt { + TerminatorKind::SwitchInt { discr, - targets: SwitchTargets::static_if(1, after_cond, end), + targets: SwitchTargets::static_if(1, after_cond, fail_cond), }, + expr_id.into(), ); + let fail_cond = this.drop_until_scope(this.drop_scopes.len() - 1, fail_cond); + let end = this.current_loop_end()?; + this.set_goto(fail_cond, end, expr_id.into()); if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? { - this.set_goto(block, begin); + let block = scope.pop_and_drop(this, block); + this.set_goto(block, begin, expr_id.into()); + } else { + scope.pop_assume_dropped(this); } Ok(()) }) } - &Expr::For { iterable, pat, body, label } => { - let into_iter_fn = self.resolve_lang_item(LangItem::IntoIterIntoIter)? - .as_function().ok_or(MirLowerError::LangItemNotFound(LangItem::IntoIterIntoIter))?; - let iter_next_fn = self.resolve_lang_item(LangItem::IteratorNext)? - .as_function().ok_or(MirLowerError::LangItemNotFound(LangItem::IteratorNext))?; - let option_some = self.resolve_lang_item(LangItem::OptionSome)? - .as_enum_variant().ok_or(MirLowerError::LangItemNotFound(LangItem::OptionSome))?; - let option = option_some.parent; - let into_iter_fn_op = Operand::const_zst( - TyKind::FnDef( - self.db.intern_callable_def(CallableDefId::FunctionId(into_iter_fn)).into(), - Substitution::from1(Interner, self.expr_ty(iterable)) - ).intern(Interner)); - let iter_next_fn_op = Operand::const_zst( - TyKind::FnDef( - self.db.intern_callable_def(CallableDefId::FunctionId(iter_next_fn)).into(), - Substitution::from1(Interner, self.expr_ty(iterable)) - ).intern(Interner)); - let &Some(iterator_ty) = &self.infer.type_of_for_iterator.get(&expr_id) else { - return Err(MirLowerError::TypeError("unknown for loop iterator type")); - }; - let ref_mut_iterator_ty = TyKind::Ref(Mutability::Mut, static_lifetime(), iterator_ty.clone()).intern(Interner); - let item_ty = &self.infer.type_of_pat[pat]; - let option_item_ty = TyKind::Adt(chalk_ir::AdtId(option.into()), Substitution::from1(Interner, item_ty.clone())).intern(Interner); - let iterator_place: Place = self.temp(iterator_ty.clone())?.into(); - let option_item_place: Place = self.temp(option_item_ty.clone())?.into(); - let ref_mut_iterator_place: Place = self.temp(ref_mut_iterator_ty)?.into(); - let Some(current) = self.lower_call_and_args(into_iter_fn_op, Some(iterable).into_iter(), iterator_place.clone(), current, false)? - else { - return Ok(None); - }; - self.push_assignment(current, ref_mut_iterator_place.clone(), Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, iterator_place), expr_id.into()); - self.lower_loop(current, label, |this, begin| { - let Some(current) = this.lower_call(iter_next_fn_op, vec![Operand::Copy(ref_mut_iterator_place)], option_item_place.clone(), begin, false)? - else { - return Ok(()); - }; - let end = this.current_loop_end()?; - let (current, _) = this.pattern_matching_variant( - option_item_ty.clone(), - BindingAnnotation::Unannotated, - option_item_place.into(), - option_some.into(), - current, - pat.into(), - Some(end), - &[pat], &None)?; - if let Some((_, block)) = this.lower_expr_as_place(current, body, true)? { - this.set_goto(block, begin); - } - Ok(()) - }) - }, Expr::Call { callee, args, .. } => { + if let Some((func_id, generic_args)) = + self.infer.method_resolution(expr_id) { + let ty = chalk_ir::TyKind::FnDef( + CallableDefId::FunctionId(func_id).to_chalk(self.db), + generic_args, + ) + .intern(Interner); + let func = Operand::from_bytes(vec![], ty); + return self.lower_call_and_args( + func, + iter::once(*callee).chain(args.iter().copied()), + place, + current, + self.is_uninhabited(expr_id), + expr_id.into(), + ); + } let callee_ty = self.expr_ty_after_adjustments(*callee); match &callee_ty.data(Interner).kind { chalk_ir::TyKind::FnDef(..) => { let func = Operand::from_bytes(vec![], callee_ty.clone()); - self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id)) + self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id), expr_id.into()) } - TyKind::Scalar(_) - | TyKind::Tuple(_, _) - | TyKind::Array(_, _) - | TyKind::Adt(_, _) - | TyKind::Str - | TyKind::Foreign(_) - | TyKind::Slice(_) => { - return Err(MirLowerError::TypeError("function call on data type")) + chalk_ir::TyKind::Function(_) => { + let Some((func, current)) = self.lower_expr_to_some_operand(*callee, current)? else { + return Ok(None); + }; + self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id), expr_id.into()) } - TyKind::Error => return Err(MirLowerError::MissingFunctionDefinition), - TyKind::AssociatedType(_, _) - | TyKind::Raw(_, _) - | TyKind::Ref(_, _, _) - | TyKind::OpaqueType(_, _) - | TyKind::Never - | TyKind::Closure(_, _) - | TyKind::Generator(_, _) - | TyKind::GeneratorWitness(_, _) - | TyKind::Placeholder(_) - | TyKind::Dyn(_) - | TyKind::Alias(_) - | TyKind::Function(_) - | TyKind::BoundVar(_) - | TyKind::InferenceVar(_, _) => not_supported!("dynamic function call"), + TyKind::Error => return Err(MirLowerError::MissingFunctionDefinition(self.owner, expr_id)), + _ => return Err(MirLowerError::TypeError("function call on bad type")), } } - Expr::MethodCall { receiver, args, .. } => { + Expr::MethodCall { receiver, args, method_name, .. } => { let (func_id, generic_args) = - self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedMethod)?; - let ty = chalk_ir::TyKind::FnDef( - CallableDefId::FunctionId(func_id).to_chalk(self.db), - generic_args, - ) - .intern(Interner); - let func = Operand::from_bytes(vec![], ty); + self.infer.method_resolution(expr_id).ok_or_else(|| MirLowerError::UnresolvedMethod(method_name.display(self.db.upcast()).to_string()))?; + let func = Operand::from_fn(self.db, func_id, generic_args); self.lower_call_and_args( func, iter::once(*receiver).chain(args.iter().copied()), place, current, self.is_uninhabited(expr_id), + expr_id.into(), ) } Expr::Match { expr, arms } => { @@ -488,23 +634,27 @@ fn lower_expr_to_place_without_adjust( else { return Ok(None); }; - let cond_ty = self.expr_ty_after_adjustments(*expr); let mut end = None; for MatchArm { pat, guard, expr } in arms.iter() { - if guard.is_some() { - not_supported!("pattern matching with guard"); - } - let (then, otherwise) = self.pattern_match( + let (then, mut otherwise) = self.pattern_match( current, None, cond_place.clone(), - cond_ty.clone(), *pat, - BindingAnnotation::Unannotated, )?; + let then = if let &Some(guard) = guard { + let next = self.new_basic_block(); + let o = otherwise.get_or_insert_with(|| self.new_basic_block()); + if let Some((discr, c)) = self.lower_expr_to_some_operand(guard, then)? { + self.set_terminator(c, TerminatorKind::SwitchInt { discr, targets: SwitchTargets::static_if(1, next, *o) }, expr_id.into()); + } + next + } else { + then + }; if let Some(block) = self.lower_expr_to_place(*expr, place.clone(), then)? { let r = end.get_or_insert_with(|| self.new_basic_block()); - self.set_goto(block, *r); + self.set_goto(block, *r, expr_id.into()); } match otherwise { Some(o) => current = o, @@ -516,32 +666,43 @@ fn lower_expr_to_place_without_adjust( } } if self.is_unterminated(current) { - self.set_terminator(current, Terminator::Unreachable); + self.set_terminator(current, TerminatorKind::Unreachable, expr_id.into()); } Ok(end) } - Expr::Continue { label } => match label { - Some(_) => not_supported!("continue with label"), - None => { - let loop_data = - self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?; - self.set_goto(current, loop_data.begin); - Ok(None) - } + Expr::Continue { label } => { + let loop_data = match label { + Some(l) => self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?, + None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::ContinueWithoutLoop)?, + }; + let begin = loop_data.begin; + current = self.drop_until_scope(loop_data.drop_scope_index, current); + self.set_goto(current, begin, expr_id.into()); + Ok(None) }, - Expr::Break { expr, label } => { - if expr.is_some() { - not_supported!("break with value"); + &Expr::Break { expr, label } => { + if let Some(expr) = expr { + let loop_data = match label { + Some(l) => self.labeled_loop_blocks.get(&l).ok_or(MirLowerError::UnresolvedLabel)?, + None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::BreakWithoutLoop)?, + }; + let Some(c) = self.lower_expr_to_place(expr, loop_data.place.clone(), current)? else { + return Ok(None); + }; + current = c; } - match label { - Some(_) => not_supported!("break with label"), + let (end, drop_scope) = match label { + Some(l) => { + let loop_blocks = self.labeled_loop_blocks.get(&l).ok_or(MirLowerError::UnresolvedLabel)?; + (loop_blocks.end.expect("We always generate end for labeled loops"), loop_blocks.drop_scope_index) + }, None => { - let end = - self.current_loop_end()?; - self.set_goto(current, end); - Ok(None) - } - } + (self.current_loop_end()?, self.current_loop_blocks.as_ref().unwrap().drop_scope_index) + }, + }; + current = self.drop_until_scope(drop_scope, current); + self.set_goto(current, end, expr_id.into()); + Ok(None) } Expr::Return { expr } => { if let Some(expr) = expr { @@ -551,11 +712,22 @@ fn lower_expr_to_place_without_adjust( return Ok(None); } } - self.set_terminator(current, Terminator::Return); + current = self.drop_until_scope(0, current); + self.set_terminator(current, TerminatorKind::Return, expr_id.into()); Ok(None) } Expr::Yield { .. } => not_supported!("yield"), - Expr::RecordLit { fields, path, .. } => { + Expr::RecordLit { fields, path, spread, ellipsis: _, is_assignee_expr: _ } => { + let spread_place = match spread { + &Some(x) => { + let Some((p, c)) = self.lower_expr_as_place(current, x, true)? else { + return Ok(None); + }; + current = c; + Some(p) + }, + None => None, + }; let variant_id = self .infer .variant_resolution_for_expr(expr_id) @@ -563,7 +735,7 @@ fn lower_expr_to_place_without_adjust( Some(p) => MirLowerError::UnresolvedName(p.display(self.db).to_string()), None => MirLowerError::RecordLiteralWithoutPath, })?; - let subst = match self.expr_ty(expr_id).kind(Interner) { + let subst = match self.expr_ty_without_adjust(expr_id).kind(Interner) { TyKind::Adt(_, s) => s.clone(), _ => not_supported!("Non ADT record literal"), }; @@ -585,9 +757,23 @@ fn lower_expr_to_place_without_adjust( place, Rvalue::Aggregate( AggregateKind::Adt(variant_id, subst), - operands.into_iter().map(|x| x).collect::>().ok_or( - MirLowerError::TypeError("missing field in record literal"), - )?, + match spread_place { + Some(sp) => operands.into_iter().enumerate().map(|(i, x)| { + match x { + Some(x) => x, + None => { + let p = sp.project(ProjectionElem::Field(FieldId { + parent: variant_id, + local_id: LocalFieldId::from_raw(RawIdx::from(i as u32)), + })); + Operand::Copy(p) + }, + } + }).collect(), + None => operands.into_iter().collect::>().ok_or( + MirLowerError::TypeError("missing field in record literal"), + )?, + }, ), expr_id.into(), ); @@ -599,20 +785,19 @@ fn lower_expr_to_place_without_adjust( }; let local_id = variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?; - let mut place = place; - place - .projection - .push(PlaceElem::Field(FieldId { parent: union_id.into(), local_id })); + let place = place.project(PlaceElem::Field(FieldId { parent: union_id.into(), local_id })); self.lower_expr_to_place(*expr, place, current) } } } Expr::Await { .. } => not_supported!("await"), - Expr::Try { .. } => not_supported!("? operator"), Expr::Yeet { .. } => not_supported!("yeet"), - Expr::TryBlock { .. } => not_supported!("try block"), Expr::Async { .. } => not_supported!("async block"), - Expr::Const { .. } => not_supported!("anonymous const block"), + &Expr::Const(id) => { + let subst = self.placeholder_subst(); + self.lower_const(id.into(), current, place, subst, expr_id.into(), self.expr_ty_without_adjust(expr_id))?; + Ok(Some(current)) + }, Expr::Cast { expr, type_ref: _ } => { let Some((x, current)) = self.lower_expr_to_some_operand(*expr, current)? else { return Ok(None); @@ -635,21 +820,30 @@ fn lower_expr_to_place_without_adjust( self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into()); Ok(Some(current)) } - Expr::Box { .. } => not_supported!("box expression"), - Expr::Field { .. } | Expr::Index { .. } | Expr::UnaryOp { op: hir_def::expr::UnaryOp::Deref, .. } => { + Expr::Box { expr } => { + let ty = self.expr_ty_after_adjustments(*expr); + self.push_assignment(current, place.clone(), Rvalue::ShallowInitBoxWithAlloc(ty), expr_id.into()); + let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)? else { + return Ok(None); + }; + let p = place.project(ProjectionElem::Deref); + self.push_assignment(current, p, operand.into(), expr_id.into()); + Ok(Some(current)) + }, + Expr::Field { .. } | Expr::Index { .. } | Expr::UnaryOp { op: hir_def::hir::UnaryOp::Deref, .. } => { let Some((p, current)) = self.lower_expr_as_place_without_adjust(current, expr_id, true)? else { return Ok(None); }; self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into()); Ok(Some(current)) } - Expr::UnaryOp { expr, op: op @ (hir_def::expr::UnaryOp::Not | hir_def::expr::UnaryOp::Neg) } => { + Expr::UnaryOp { expr, op: op @ (hir_def::hir::UnaryOp::Not | hir_def::hir::UnaryOp::Neg) } => { let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)? else { return Ok(None); }; let operation = match op { - hir_def::expr::UnaryOp::Not => UnOp::Not, - hir_def::expr::UnaryOp::Neg => UnOp::Neg, + hir_def::hir::UnaryOp::Not => UnOp::Not, + hir_def::hir::UnaryOp::Neg => UnOp::Neg, _ => unreachable!(), }; self.push_assignment( @@ -662,24 +856,93 @@ fn lower_expr_to_place_without_adjust( }, Expr::BinaryOp { lhs, rhs, op } => { let op = op.ok_or(MirLowerError::IncompleteExpr)?; - if let hir_def::expr::BinaryOp::Assignment { op } = op { - if op.is_some() { - not_supported!("assignment with arith op (like +=)"); + let is_builtin = 'b: { + // Without adjust here is a hack. We assume that we know every possible adjustment + // for binary operator, and use without adjust to simplify our conditions. + let lhs_ty = self.expr_ty_without_adjust(*lhs); + let rhs_ty = self.expr_ty_without_adjust(*rhs); + if matches!(op ,BinaryOp::CmpOp(syntax::ast::CmpOp::Eq { .. })) { + if lhs_ty.as_raw_ptr().is_some() && rhs_ty.as_raw_ptr().is_some() { + break 'b true; + } } - let Some((lhs_place, current)) = + let builtin_inequal_impls = matches!( + op, + BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) | BinaryOp::Assignment { op: Some(ArithOp::Shl | ArithOp::Shr) } + ); + lhs_ty.is_scalar() && rhs_ty.is_scalar() && (lhs_ty == rhs_ty || builtin_inequal_impls) + }; + if !is_builtin { + if let Some((func_id, generic_args)) = self.infer.method_resolution(expr_id) { + let func = Operand::from_fn(self.db, func_id, generic_args); + return self.lower_call_and_args( + func, + [*lhs, *rhs].into_iter(), + place, + current, + self.is_uninhabited(expr_id), + expr_id.into(), + ); + } + } + if let hir_def::hir::BinaryOp::Assignment { op } = op { + if let Some(op) = op { + // last adjustment is `&mut` which we don't want it. + let adjusts = self + .infer + .expr_adjustments + .get(lhs) + .and_then(|x| x.split_last()) + .map(|x| x.1) + .ok_or(MirLowerError::TypeError("adjustment of binary op was missing"))?; + let Some((lhs_place, current)) = + self.lower_expr_as_place_with_adjust(current, *lhs, false, adjusts)? + else { + return Ok(None); + }; + let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else { + return Ok(None); + }; + let r_value = Rvalue::CheckedBinaryOp(op.into(), Operand::Copy(lhs_place.clone()), rhs_op); + self.push_assignment(current, lhs_place, r_value, expr_id.into()); + return Ok(Some(current)); + } else { + let Some((lhs_place, current)) = self.lower_expr_as_place(current, *lhs, false)? - else { - return Ok(None); - }; - let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else { - return Ok(None); - }; - self.push_assignment(current, lhs_place, rhs_op.into(), expr_id.into()); - return Ok(Some(current)); + else { + return Ok(None); + }; + let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else { + return Ok(None); + }; + self.push_assignment(current, lhs_place, rhs_op.into(), expr_id.into()); + return Ok(Some(current)); + } } let Some((lhs_op, current)) = self.lower_expr_to_some_operand(*lhs, current)? else { return Ok(None); }; + if let hir_def::hir::BinaryOp::LogicOp(op) = op { + let value_to_short = match op { + syntax::ast::LogicOp::And => 0, + syntax::ast::LogicOp::Or => 1, + }; + let start_of_then = self.new_basic_block(); + self.push_assignment(start_of_then, place.clone(), lhs_op.clone().into(), expr_id.into()); + let end_of_then = Some(start_of_then); + let start_of_else = self.new_basic_block(); + let end_of_else = + self.lower_expr_to_place(*rhs, place, start_of_else)?; + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr: lhs_op, + targets: SwitchTargets::static_if(value_to_short, start_of_then, start_of_else), + }, + expr_id.into(), + ); + return Ok(self.merge_blocks(end_of_then, end_of_else, expr_id.into())); + } let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else { return Ok(None); }; @@ -688,13 +951,13 @@ fn lower_expr_to_place_without_adjust( place, Rvalue::CheckedBinaryOp( match op { - hir_def::expr::BinaryOp::LogicOp(op) => match op { - hir_def::expr::LogicOp::And => BinOp::BitAnd, // FIXME: make these short circuit - hir_def::expr::LogicOp::Or => BinOp::BitOr, + hir_def::hir::BinaryOp::LogicOp(op) => match op { + hir_def::hir::LogicOp::And => BinOp::BitAnd, // FIXME: make these short circuit + hir_def::hir::LogicOp::Or => BinOp::BitOr, }, - hir_def::expr::BinaryOp::ArithOp(op) => BinOp::from(op), - hir_def::expr::BinaryOp::CmpOp(op) => BinOp::from(op), - hir_def::expr::BinaryOp::Assignment { .. } => unreachable!(), // handled above + hir_def::hir::BinaryOp::ArithOp(op) => BinOp::from(op), + hir_def::hir::BinaryOp::CmpOp(op) => BinOp::from(op), + hir_def::hir::BinaryOp::Assignment { .. } => unreachable!(), // handled above }, lhs_op, rhs_op, @@ -703,8 +966,96 @@ fn lower_expr_to_place_without_adjust( ); Ok(Some(current)) } - Expr::Range { .. } => not_supported!("range"), - Expr::Closure { .. } => not_supported!("closure"), + &Expr::Range { lhs, rhs, range_type: _ } => { + let ty = self.expr_ty_without_adjust(expr_id); + let Some((adt, subst)) = ty.as_adt() else { + return Err(MirLowerError::TypeError("Range type is not adt")); + }; + let AdtId::StructId(st) = adt else { + return Err(MirLowerError::TypeError("Range type is not struct")); + }; + let mut lp = None; + let mut rp = None; + if let Some(x) = lhs { + let Some((o, c)) = self.lower_expr_to_some_operand(x, current)? else { + return Ok(None); + }; + lp = Some(o); + current = c; + } + if let Some(x) = rhs { + let Some((o, c)) = self.lower_expr_to_some_operand(x, current)? else { + return Ok(None); + }; + rp = Some(o); + current = c; + } + self.push_assignment( + current, + place, + Rvalue::Aggregate( + AggregateKind::Adt(st.into(), subst.clone()), + self.db.struct_data(st).variant_data.fields().iter().map(|x| { + let o = match x.1.name.as_str() { + Some("start") => lp.take(), + Some("end") => rp.take(), + Some("exhausted") => Some(Operand::from_bytes(vec![0], TyBuilder::bool())), + _ => None, + }; + o.ok_or(MirLowerError::UnresolvedField) + }).collect::>()?, + ), + expr_id.into(), + ); + Ok(Some(current)) + }, + Expr::Closure { .. } => { + let ty = self.expr_ty_without_adjust(expr_id); + let TyKind::Closure(id, _) = ty.kind(Interner) else { + not_supported!("closure with non closure type"); + }; + self.result.closures.push(*id); + let (captures, _) = self.infer.closure_info(id); + let mut operands = vec![]; + for capture in captures.iter() { + let p = Place { + local: self.binding_local(capture.place.local)?, + projection: capture.place.projections.clone().into_iter().map(|x| { + match x { + ProjectionElem::Deref => ProjectionElem::Deref, + ProjectionElem::Field(x) => ProjectionElem::Field(x), + ProjectionElem::TupleOrClosureField(x) => ProjectionElem::TupleOrClosureField(x), + ProjectionElem::ConstantIndex { offset, from_end } => ProjectionElem::ConstantIndex { offset, from_end }, + ProjectionElem::Subslice { from, to } => ProjectionElem::Subslice { from, to }, + ProjectionElem::OpaqueCast(x) => ProjectionElem::OpaqueCast(x), + ProjectionElem::Index(x) => match x { }, + } + }).collect(), + }; + match &capture.kind { + CaptureKind::ByRef(bk) => { + let placeholder_subst = self.placeholder_subst(); + let tmp_ty = capture.ty.clone().substitute(Interner, &placeholder_subst); + let tmp: Place = self.temp(tmp_ty, current, capture.span)?.into(); + self.push_assignment( + current, + tmp.clone(), + Rvalue::Ref(bk.clone(), p), + capture.span, + ); + operands.push(Operand::Move(tmp)); + }, + CaptureKind::ByValue => operands.push(Operand::Move(p)), + } + } + self.push_assignment( + current, + place, + Rvalue::Aggregate(AggregateKind::Closure(ty), operands.into()), + expr_id.into(), + ); + Ok(Some(current)) + }, Expr::Tuple { exprs, is_assignee_expr: _ } => { let Some(values) = exprs .iter() @@ -720,7 +1071,7 @@ fn lower_expr_to_place_without_adjust( return Ok(None); }; let r = Rvalue::Aggregate( - AggregateKind::Tuple(self.expr_ty(expr_id)), + AggregateKind::Tuple(self.expr_ty_without_adjust(expr_id)), values, ); self.push_assignment(current, place, r, expr_id.into()); @@ -728,7 +1079,7 @@ fn lower_expr_to_place_without_adjust( } Expr::Array(l) => match l { Array::ElementList { elements, .. } => { - let elem_ty = match &self.expr_ty(expr_id).data(Interner).kind { + let elem_ty = match &self.expr_ty_without_adjust(expr_id).data(Interner).kind { TyKind::Array(ty, _) => ty.clone(), _ => { return Err(MirLowerError::TypeError( @@ -756,10 +1107,25 @@ fn lower_expr_to_place_without_adjust( self.push_assignment(current, place, r, expr_id.into()); Ok(Some(current)) } - Array::Repeat { .. } => not_supported!("array repeat"), + Array::Repeat { initializer, .. } => { + let Some((init, current)) = self.lower_expr_to_some_operand(*initializer, current)? else { + return Ok(None); + }; + let len = match &self.expr_ty_without_adjust(expr_id).data(Interner).kind { + TyKind::Array(_, len) => len.clone(), + _ => { + return Err(MirLowerError::TypeError( + "Array repeat expression with non array type", + )) + } + }; + let r = Rvalue::Repeat(init, len); + self.push_assignment(current, place, r, expr_id.into()); + Ok(Some(current)) + }, }, Expr::Literal(l) => { - let ty = self.expr_ty(expr_id); + let ty = self.expr_ty_without_adjust(expr_id); let op = self.lower_literal_to_operand(ty, l)?; self.push_assignment(current, place, op.into(), expr_id.into()); Ok(Some(current)) @@ -768,17 +1134,25 @@ fn lower_expr_to_place_without_adjust( } } + fn placeholder_subst(&mut self) -> Substitution { + let placeholder_subst = match self.owner.as_generic_def_id() { + Some(x) => TyBuilder::placeholder_subst(self.db, x), + None => Substitution::empty(Interner), + }; + placeholder_subst + } + fn push_field_projection(&self, place: &mut Place, expr_id: ExprId) -> Result<()> { if let Expr::Field { expr, name } = &self.body[expr_id] { if let TyKind::Tuple(..) = self.expr_ty_after_adjustments(*expr).kind(Interner) { let index = name .as_tuple_index() .ok_or(MirLowerError::TypeError("named field on tuple"))?; - place.projection.push(ProjectionElem::TupleField(index)) + *place = place.project(ProjectionElem::TupleOrClosureField(index)) } else { let field = self.infer.field_resolution(expr_id).ok_or(MirLowerError::UnresolvedField)?; - place.projection.push(ProjectionElem::Field(field)); + *place = place.project(ProjectionElem::Field(field)); } } else { not_supported!("") @@ -786,33 +1160,75 @@ fn push_field_projection(&self, place: &mut Place, expr_id: ExprId) -> Result<() Ok(()) } + fn lower_literal_or_const_to_operand( + &mut self, + ty: Ty, + loc: &LiteralOrConst, + ) -> Result { + match loc { + LiteralOrConst::Literal(l) => self.lower_literal_to_operand(ty, l), + LiteralOrConst::Const(c) => { + let unresolved_name = || MirLowerError::unresolved_path(self.db, c); + let resolver = self.owner.resolver(self.db.upcast()); + let pr = resolver + .resolve_path_in_value_ns(self.db.upcast(), c) + .ok_or_else(unresolved_name)?; + match pr { + ResolveValueResult::ValueNs(v) => { + if let ValueNs::ConstId(c) = v { + self.lower_const_to_operand(Substitution::empty(Interner), c.into(), ty) + } else { + not_supported!("bad path in range pattern"); + } + } + ResolveValueResult::Partial(_, _) => { + not_supported!("associated constants in range pattern") + } + } + } + } + } + fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result { - let size = layout_of_ty(self.db, &ty, self.owner.module(self.db.upcast()).krate())? + let size = self + .db + .layout_of_ty(ty.clone(), self.owner.module(self.db.upcast()).krate())? .size .bytes_usize(); let bytes = match l { - hir_def::expr::Literal::String(b) => { + hir_def::hir::Literal::String(b) => { let b = b.as_bytes(); - let mut data = vec![]; + let mut data = Vec::with_capacity(mem::size_of::() * 2); data.extend(0usize.to_le_bytes()); data.extend(b.len().to_le_bytes()); let mut mm = MemoryMap::default(); mm.insert(0, b.to_vec()); return Ok(Operand::from_concrete_const(data, mm, ty)); } - hir_def::expr::Literal::ByteString(b) => { - let mut data = vec![]; + hir_def::hir::Literal::CString(b) => { + let b = b.as_bytes(); + let bytes = b.iter().copied().chain(iter::once(0)).collect::>(); + + let mut data = Vec::with_capacity(mem::size_of::() * 2); + data.extend(0usize.to_le_bytes()); + data.extend(bytes.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, bytes); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::hir::Literal::ByteString(b) => { + let mut data = Vec::with_capacity(mem::size_of::() * 2); data.extend(0usize.to_le_bytes()); data.extend(b.len().to_le_bytes()); let mut mm = MemoryMap::default(); mm.insert(0, b.to_vec()); return Ok(Operand::from_concrete_const(data, mm, ty)); } - hir_def::expr::Literal::Char(c) => u32::from(*c).to_le_bytes().into(), - hir_def::expr::Literal::Bool(b) => vec![*b as u8], - hir_def::expr::Literal::Int(x, _) => x.to_le_bytes()[0..size].into(), - hir_def::expr::Literal::Uint(x, _) => x.to_le_bytes()[0..size].into(), - hir_def::expr::Literal::Float(f, _) => match size { + hir_def::hir::Literal::Char(c) => u32::from(*c).to_le_bytes().into(), + hir_def::hir::Literal::Bool(b) => vec![*b as u8], + hir_def::hir::Literal::Int(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::hir::Literal::Uint(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::hir::Literal::Float(f, _) => match size { 8 => f.into_f64().to_le_bytes().into(), 4 => f.into_f32().to_le_bytes().into(), _ => { @@ -829,24 +1245,34 @@ fn new_basic_block(&mut self) -> BasicBlockId { fn lower_const( &mut self, - const_id: hir_def::ConstId, + const_id: GeneralConstId, prev_block: BasicBlockId, place: Place, + subst: Substitution, span: MirSpan, + ty: Ty, ) -> Result<()> { - let c = self.db.const_eval(const_id)?; - self.write_const_to_place(c, prev_block, place, span) + let c = self.lower_const_to_operand(subst, const_id, ty)?; + self.push_assignment(prev_block, place, c.into(), span); + Ok(()) } - fn write_const_to_place( + fn lower_const_to_operand( &mut self, - c: Const, - prev_block: BasicBlockId, - place: Place, - span: MirSpan, - ) -> Result<()> { - self.push_assignment(prev_block, place, Operand::Constant(c).into(), span); - Ok(()) + subst: Substitution, + const_id: GeneralConstId, + ty: Ty, + ) -> Result { + let c = if subst.len(Interner) != 0 { + // We can't evaluate constant with substitution now, as generics are not monomorphized in lowering. + intern_const_scalar(ConstScalar::UnevaluatedConst(const_id, subst), ty) + } else { + let name = const_id.name(self.db.upcast()); + self.db + .const_eval(const_id.into(), subst) + .map_err(|e| MirLowerError::ConstEvalError(name, Box::new(e)))? + }; + Ok(Operand::Constant(c)) } fn write_bytes_to_place( @@ -867,12 +1293,12 @@ fn lower_enum_variant( prev_block: BasicBlockId, place: Place, ty: Ty, - fields: Vec, + fields: Box<[Operand]>, span: MirSpan, ) -> Result { let subst = match ty.kind(Interner) { TyKind::Adt(_, subst) => subst.clone(), - _ => not_supported!("Non ADT enum"), + _ => implementation_error!("Non ADT enum"), }; self.push_assignment( prev_block, @@ -890,6 +1316,7 @@ fn lower_call_and_args( place: Place, mut current: BasicBlockId, is_uninhabited: bool, + span: MirSpan, ) -> Result> { let Some(args) = args .map(|arg| { @@ -904,21 +1331,22 @@ fn lower_call_and_args( else { return Ok(None); }; - self.lower_call(func, args, place, current, is_uninhabited) + self.lower_call(func, args.into(), place, current, is_uninhabited, span) } fn lower_call( &mut self, func: Operand, - args: Vec, + args: Box<[Operand]>, place: Place, current: BasicBlockId, is_uninhabited: bool, + span: MirSpan, ) -> Result> { let b = if is_uninhabited { None } else { Some(self.new_basic_block()) }; self.set_terminator( current, - Terminator::Call { + TerminatorKind::Call { func, args, destination: place, @@ -926,6 +1354,7 @@ fn lower_call( cleanup: None, from_hir_call: true, }, + span, ); Ok(b) } @@ -934,15 +1363,15 @@ fn is_unterminated(&mut self, source: BasicBlockId) -> bool { self.result.basic_blocks[source].terminator.is_none() } - fn set_terminator(&mut self, source: BasicBlockId, terminator: Terminator) { - self.result.basic_blocks[source].terminator = Some(terminator); + fn set_terminator(&mut self, source: BasicBlockId, terminator: TerminatorKind, span: MirSpan) { + self.result.basic_blocks[source].terminator = Some(Terminator { span, kind: terminator }); } - fn set_goto(&mut self, source: BasicBlockId, target: BasicBlockId) { - self.set_terminator(source, Terminator::Goto { target }); + fn set_goto(&mut self, source: BasicBlockId, target: BasicBlockId, span: MirSpan) { + self.set_terminator(source, TerminatorKind::Goto { target }, span); } - fn expr_ty(&self, e: ExprId) -> Ty { + fn expr_ty_without_adjust(&self, e: ExprId) -> Ty { self.infer[e].clone() } @@ -953,7 +1382,7 @@ fn expr_ty_after_adjustments(&self, e: ExprId) -> Ty { ty = Some(x.target.clone()); } } - ty.unwrap_or_else(|| self.expr_ty(e)) + ty.unwrap_or_else(|| self.expr_ty_without_adjust(e)) } fn push_statement(&mut self, block: BasicBlockId, statement: Statement) { @@ -970,293 +1399,14 @@ fn push_assignment( self.push_statement(block, StatementKind::Assign(place, rvalue).with_span(span)); } - /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if - /// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which - /// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the - /// mismatched path block is `None`. - /// - /// By default, it will create a new block for mismatched path. If you already have one, you can provide it with - /// `current_else` argument to save an unneccessary jump. If `current_else` isn't `None`, the result mismatched path - /// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block, - /// so it should be an empty block. - fn pattern_match( - &mut self, - mut current: BasicBlockId, - mut current_else: Option, - mut cond_place: Place, - mut cond_ty: Ty, - pattern: PatId, - mut binding_mode: BindingAnnotation, - ) -> Result<(BasicBlockId, Option)> { - Ok(match &self.body.pats[pattern] { - Pat::Missing => return Err(MirLowerError::IncompleteExpr), - Pat::Wild => (current, current_else), - Pat::Tuple { args, ellipsis } => { - pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); - let subst = match cond_ty.kind(Interner) { - TyKind::Tuple(_, s) => s, - _ => { - return Err(MirLowerError::TypeError( - "non tuple type matched with tuple pattern", - )) - } - }; - self.pattern_match_tuple_like( - current, - current_else, - args.iter().enumerate().map(|(i, x)| { - ( - PlaceElem::TupleField(i), - *x, - subst.at(Interner, i).assert_ty_ref(Interner).clone(), - ) - }), - *ellipsis, - &cond_place, - binding_mode, - )? - } - Pat::Or(pats) => { - let then_target = self.new_basic_block(); - let mut finished = false; - for pat in &**pats { - let (next, next_else) = self.pattern_match( - current, - None, - cond_place.clone(), - cond_ty.clone(), - *pat, - binding_mode, - )?; - self.set_goto(next, then_target); - match next_else { - Some(t) => { - current = t; - } - None => { - finished = true; - break; - } - } - } - if !finished { - let ce = *current_else.get_or_insert_with(|| self.new_basic_block()); - self.set_goto(current, ce); - } - (then_target, current_else) - } - Pat::Record { .. } => not_supported!("record pattern"), - Pat::Range { .. } => not_supported!("range pattern"), - Pat::Slice { .. } => not_supported!("slice pattern"), - Pat::Path(_) => { - let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { - not_supported!("unresolved variant"); - }; - self.pattern_matching_variant( - cond_ty, - binding_mode, - cond_place, - variant, - current, - pattern.into(), - current_else, - &[], - &None, - )? - } - Pat::Lit(l) => { - let then_target = self.new_basic_block(); - let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); - match &self.body.exprs[*l] { - Expr::Literal(l) => match l { - hir_def::expr::Literal::Int(x, _) => { - self.set_terminator( - current, - Terminator::SwitchInt { - discr: Operand::Copy(cond_place), - targets: SwitchTargets::static_if( - *x as u128, - then_target, - else_target, - ), - }, - ); - } - hir_def::expr::Literal::Uint(x, _) => { - self.set_terminator( - current, - Terminator::SwitchInt { - discr: Operand::Copy(cond_place), - targets: SwitchTargets::static_if(*x, then_target, else_target), - }, - ); - } - _ => not_supported!("non int path literal"), - }, - _ => not_supported!("expression path literal"), - } - (then_target, Some(else_target)) - } - Pat::Bind { id, subpat } => { - let target_place = self.result.binding_locals[*id]; - let mode = self.body.bindings[*id].mode; - if let Some(subpat) = subpat { - (current, current_else) = self.pattern_match( - current, - current_else, - cond_place.clone(), - cond_ty, - *subpat, - binding_mode, - )? - } - if matches!(mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) { - binding_mode = mode; - } - self.push_storage_live(*id, current); - self.push_assignment( - current, - target_place.into(), - match binding_mode { - BindingAnnotation::Unannotated | BindingAnnotation::Mutable => { - Operand::Copy(cond_place).into() - } - BindingAnnotation::Ref => Rvalue::Ref(BorrowKind::Shared, cond_place), - BindingAnnotation::RefMut => Rvalue::Ref( - BorrowKind::Mut { allow_two_phase_borrow: false }, - cond_place, - ), - }, - pattern.into(), - ); - (current, current_else) - } - Pat::TupleStruct { path: _, args, ellipsis } => { - let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { - not_supported!("unresolved variant"); - }; - self.pattern_matching_variant( - cond_ty, - binding_mode, - cond_place, - variant, - current, - pattern.into(), - current_else, - args, - ellipsis, - )? - } - Pat::Ref { .. } => not_supported!("& pattern"), - Pat::Box { .. } => not_supported!("box pattern"), - Pat::ConstBlock(_) => not_supported!("const block pattern"), - }) - } - - fn pattern_matching_variant( - &mut self, - mut cond_ty: Ty, - mut binding_mode: BindingAnnotation, - mut cond_place: Place, - variant: VariantId, - current: BasicBlockId, - span: MirSpan, - current_else: Option, - args: &[PatId], - ellipsis: &Option, - ) -> Result<(BasicBlockId, Option)> { - pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); - let subst = match cond_ty.kind(Interner) { - TyKind::Adt(_, s) => s, - _ => return Err(MirLowerError::TypeError("non adt type matched with tuple struct")), - }; - let fields_type = self.db.field_types(variant); - Ok(match variant { - VariantId::EnumVariantId(v) => { - let e = self.db.const_eval_discriminant(v)? as u128; - let next = self.new_basic_block(); - let tmp = self.discr_temp_place(); - self.push_assignment( - current, - tmp.clone(), - Rvalue::Discriminant(cond_place.clone()), - span, - ); - let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); - self.set_terminator( - current, - Terminator::SwitchInt { - discr: Operand::Copy(tmp), - targets: SwitchTargets::static_if(e, next, else_target), - }, - ); - let enum_data = self.db.enum_data(v.parent); - let fields = - enum_data.variants[v.local_id].variant_data.fields().iter().map(|(x, _)| { - ( - PlaceElem::Field(FieldId { parent: v.into(), local_id: x }), - fields_type[x].clone().substitute(Interner, subst), - ) - }); - self.pattern_match_tuple_like( - next, - Some(else_target), - args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), - *ellipsis, - &cond_place, - binding_mode, - )? - } - VariantId::StructId(s) => { - let struct_data = self.db.struct_data(s); - let fields = struct_data.variant_data.fields().iter().map(|(x, _)| { - ( - PlaceElem::Field(FieldId { parent: s.into(), local_id: x }), - fields_type[x].clone().substitute(Interner, subst), - ) - }); - self.pattern_match_tuple_like( - current, - current_else, - args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), - *ellipsis, - &cond_place, - binding_mode, - )? - } - VariantId::UnionId(_) => { - return Err(MirLowerError::TypeError("pattern matching on union")) - } - }) - } - - fn pattern_match_tuple_like( - &mut self, - mut current: BasicBlockId, - mut current_else: Option, - args: impl Iterator, - ellipsis: Option, - cond_place: &Place, - binding_mode: BindingAnnotation, - ) -> Result<(BasicBlockId, Option)> { - if ellipsis.is_some() { - not_supported!("tuple like pattern with ellipsis"); - } - for (proj, arg, ty) in args { - let mut cond_place = cond_place.clone(); - cond_place.projection.push(proj); - (current, current_else) = - self.pattern_match(current, current_else, cond_place, ty, arg, binding_mode)?; - } - Ok((current, current_else)) - } - - fn discr_temp_place(&mut self) -> Place { + fn discr_temp_place(&mut self, current: BasicBlockId) -> Place { match &self.discr_temp { Some(x) => x.clone(), None => { - let tmp: Place = - self.temp(TyBuilder::discr_ty()).expect("discr_ty is never unsized").into(); + let tmp: Place = self + .temp(TyBuilder::discr_ty(), current, MirSpan::Unknown) + .expect("discr_ty is never unsized") + .into(); self.discr_temp = Some(tmp.clone()); tmp } @@ -1266,19 +1416,34 @@ fn discr_temp_place(&mut self) -> Place { fn lower_loop( &mut self, prev_block: BasicBlockId, + place: Place, label: Option, + span: MirSpan, f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId) -> Result<()>, ) -> Result> { - if label.is_some() { - not_supported!("loop with label"); - } let begin = self.new_basic_block(); - let prev = - mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end: None })); - self.set_goto(prev_block, begin); + let prev = mem::replace( + &mut self.current_loop_blocks, + Some(LoopBlocks { begin, end: None, place, drop_scope_index: self.drop_scopes.len() }), + ); + let prev_label = if let Some(label) = label { + // We should generate the end now, to make sure that it wouldn't change later. It is + // bad as we may emit end (unnecessary unreachable block) for unterminating loop, but + // it should not affect correctness. + self.current_loop_end()?; + self.labeled_loop_blocks + .insert(label, self.current_loop_blocks.as_ref().unwrap().clone()) + } else { + None + }; + self.set_goto(prev_block, begin, span); f(self, begin)?; - let my = mem::replace(&mut self.current_loop_blocks, prev) - .ok_or(MirLowerError::ImplementationError("current_loop_blocks is corrupt"))?; + let my = mem::replace(&mut self.current_loop_blocks, prev).ok_or( + MirLowerError::ImplementationError("current_loop_blocks is corrupt".to_string()), + )?; + if let Some(prev) = prev_label { + self.labeled_loop_blocks.insert(label.unwrap(), prev); + } Ok(my.end) } @@ -1290,14 +1455,15 @@ fn merge_blocks( &mut self, b1: Option, b2: Option, + span: MirSpan, ) -> Option { match (b1, b2) { (None, None) => None, (None, Some(b)) | (Some(b), None) => Some(b), (Some(b1), Some(b2)) => { let bm = self.new_basic_block(); - self.set_goto(b1, bm); - self.set_goto(b2, bm); + self.set_goto(b1, bm, span); + self.set_goto(b2, bm, span); Some(bm) } } @@ -1307,7 +1473,9 @@ fn current_loop_end(&mut self) -> Result { let r = match self .current_loop_blocks .as_mut() - .ok_or(MirLowerError::ImplementationError("Current loop access out of loop"))? + .ok_or(MirLowerError::ImplementationError( + "Current loop access out of loop".to_string(), + ))? .end { Some(x) => x, @@ -1315,7 +1483,9 @@ fn current_loop_end(&mut self) -> Result { let s = self.new_basic_block(); self.current_loop_blocks .as_mut() - .ok_or(MirLowerError::ImplementationError("Current loop access out of loop"))? + .ok_or(MirLowerError::ImplementationError( + "Current loop access out of loop".to_string(), + ))? .end = Some(s); s } @@ -1327,36 +1497,28 @@ fn is_uninhabited(&self, expr_id: ExprId) -> bool { is_ty_uninhabited_from(&self.infer[expr_id], self.owner.module(self.db.upcast()), self.db) } - /// This function push `StorageLive` statement for the binding, and applies changes to add `StorageDead` in - /// the appropriated places. - fn push_storage_live(&mut self, b: BindingId, current: BasicBlockId) { - // Current implementation is wrong. It adds no `StorageDead` at the end of scope, and before each break - // and continue. It just add a `StorageDead` before the `StorageLive`, which is not wrong, but unneeeded in - // the proper implementation. Due this limitation, implementing a borrow checker on top of this mir will falsely - // allow this: - // - // ``` - // let x; - // loop { - // let y = 2; - // x = &y; - // if some_condition { - // break; // we need to add a StorageDead(y) above this to kill the x borrow - // } - // } - // use(x) - // ``` - // But I think this approach work for mutability analysis, as user can't write code which mutates a binding - // after StorageDead, except loops, which are handled by this hack. + /// This function push `StorageLive` statement for the binding, and applies changes to add `StorageDead` and + /// `Drop` in the appropriated places. + fn push_storage_live(&mut self, b: BindingId, current: BasicBlockId) -> Result<()> { let span = self.body.bindings[b] .definitions .first() .copied() .map(MirSpan::PatId) .unwrap_or(MirSpan::Unknown); - let l = self.result.binding_locals[b]; - self.push_statement(current, StatementKind::StorageDead(l).with_span(span)); + let l = self.binding_local(b)?; + self.push_storage_live_for_local(l, current, span) + } + + fn push_storage_live_for_local( + &mut self, + l: LocalId, + current: BasicBlockId, + span: MirSpan, + ) -> Result<()> { + self.drop_scopes.last_mut().unwrap().locals.push(l); self.push_statement(current, StatementKind::StorageLive(l).with_span(span)); + Ok(()) } fn resolve_lang_item(&self, item: LangItem) -> Result { @@ -1366,81 +1528,204 @@ fn resolve_lang_item(&self, item: LangItem) -> Result { fn lower_block_to_place( &mut self, - label: Option, - statements: &[hir_def::expr::Statement], + statements: &[hir_def::hir::Statement], mut current: BasicBlockId, tail: Option, place: Place, + span: MirSpan, ) -> Result>> { - if label.is_some() { - not_supported!("block with label"); - } + let scope = self.push_drop_scope(); for statement in statements.iter() { match statement { - hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => { + hir_def::hir::Statement::Let { pat, initializer, else_branch, type_ref: _ } => { if let Some(expr_id) = initializer { let else_block; let Some((init_place, c)) = self.lower_expr_as_place(current, *expr_id, true)? else { + scope.pop_assume_dropped(self); return Ok(None); }; current = c; - (current, else_block) = self.pattern_match( - current, - None, - init_place, - self.expr_ty_after_adjustments(*expr_id), - *pat, - BindingAnnotation::Unannotated, - )?; + (current, else_block) = + self.pattern_match(current, None, init_place, *pat)?; match (else_block, else_branch) { (None, _) => (), (Some(else_block), None) => { - self.set_terminator(else_block, Terminator::Unreachable); + self.set_terminator(else_block, TerminatorKind::Unreachable, span); } (Some(else_block), Some(else_branch)) => { if let Some((_, b)) = self.lower_expr_as_place(else_block, *else_branch, true)? { - self.set_terminator(b, Terminator::Unreachable); + self.set_terminator(b, TerminatorKind::Unreachable, span); } } } } else { + let mut err = None; self.body.walk_bindings_in_pat(*pat, |b| { - self.push_storage_live(b, current); + if let Err(e) = self.push_storage_live(b, current) { + err = Some(e); + } }); + if let Some(e) = err { + return Err(e); + } } } - hir_def::expr::Statement::Expr { expr, has_semi: _ } => { + hir_def::hir::Statement::Expr { expr, has_semi: _ } => { + let scope2 = self.push_drop_scope(); let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else { + scope2.pop_assume_dropped(self); + scope.pop_assume_dropped(self); return Ok(None); }; - current = c; + current = scope2.pop_and_drop(self, c); } } } - match tail { - Some(tail) => self.lower_expr_to_place(tail, place, current), - None => Ok(Some(current)), + if let Some(tail) = tail { + let Some(c) = self.lower_expr_to_place(tail, place, current)? else { + scope.pop_assume_dropped(self); + return Ok(None); + }; + current = c; + } + current = scope.pop_and_drop(self, current); + Ok(Some(current)) + } + + fn lower_params_and_bindings( + &mut self, + params: impl Iterator + Clone, + pick_binding: impl Fn(BindingId) -> bool, + ) -> Result { + let base_param_count = self.result.param_locals.len(); + self.result.param_locals.extend(params.clone().map(|(x, ty)| { + let local_id = self.result.locals.alloc(Local { ty }); + self.drop_scopes.last_mut().unwrap().locals.push(local_id); + if let Pat::Bind { id, subpat: None } = self.body[x] { + if matches!( + self.body.bindings[id].mode, + BindingAnnotation::Unannotated | BindingAnnotation::Mutable + ) { + self.result.binding_locals.insert(id, local_id); + } + } + local_id + })); + // and then rest of bindings + for (id, _) in self.body.bindings.iter() { + if !pick_binding(id) { + continue; + } + if !self.result.binding_locals.contains_idx(id) { + self.result + .binding_locals + .insert(id, self.result.locals.alloc(Local { ty: self.infer[id].clone() })); + } + } + let mut current = self.result.start_block; + for ((param, _), local) in + params.zip(self.result.param_locals.clone().into_iter().skip(base_param_count)) + { + if let Pat::Bind { id, .. } = self.body[param] { + if local == self.binding_local(id)? { + continue; + } + } + let r = self.pattern_match(current, None, local.into(), param)?; + if let Some(b) = r.1 { + self.set_terminator(b, TerminatorKind::Unreachable, param.into()); + } + current = r.0; + } + Ok(current) + } + + fn binding_local(&self, b: BindingId) -> Result { + match self.result.binding_locals.get(b) { + Some(x) => Ok(*x), + None => { + // FIXME: It should never happens, but currently it will happen in `const_dependent_on_local` test, which + // is a hir lowering problem IMO. + // never!("Using unaccessable local for binding is always a bug"); + Err(MirLowerError::UnaccessableLocal) + } } } -} -fn pattern_matching_dereference( - cond_ty: &mut Ty, - binding_mode: &mut BindingAnnotation, - cond_place: &mut Place, -) { - while let Some((ty, _, mu)) = cond_ty.as_reference() { - if mu == Mutability::Mut && *binding_mode != BindingAnnotation::Ref { - *binding_mode = BindingAnnotation::RefMut; - } else { - *binding_mode = BindingAnnotation::Ref; + fn const_eval_discriminant(&self, variant: EnumVariantId) -> Result { + let r = self.db.const_eval_discriminant(variant); + match r { + Ok(r) => Ok(r), + Err(e) => { + let data = self.db.enum_data(variant.parent); + let name = format!( + "{}::{}", + data.name.display(self.db.upcast()), + data.variants[variant.local_id].name.display(self.db.upcast()) + ); + Err(MirLowerError::ConstEvalError(name, Box::new(e))) + } + } + } + + fn drop_until_scope(&mut self, scope_index: usize, mut current: BasicBlockId) -> BasicBlockId { + for scope in self.drop_scopes[scope_index..].to_vec().iter().rev() { + self.emit_drop_and_storage_dead_for_scope(scope, &mut current); + } + current + } + + fn push_drop_scope(&mut self) -> DropScopeToken { + self.drop_scopes.push(DropScope::default()); + DropScopeToken + } + + /// Don't call directly + fn pop_drop_scope_assume_dropped_internal(&mut self) { + self.drop_scopes.pop(); + } + + /// Don't call directly + fn pop_drop_scope_internal(&mut self, mut current: BasicBlockId) -> BasicBlockId { + let scope = self.drop_scopes.pop().unwrap(); + self.emit_drop_and_storage_dead_for_scope(&scope, &mut current); + current + } + + fn pop_drop_scope_assert_finished( + &mut self, + mut current: BasicBlockId, + ) -> Result { + current = self.pop_drop_scope_internal(current); + if !self.drop_scopes.is_empty() { + implementation_error!("Mismatched count between drop scope push and pops"); + } + Ok(current) + } + + fn emit_drop_and_storage_dead_for_scope( + &mut self, + scope: &DropScope, + current: &mut Idx, + ) { + for &l in scope.locals.iter().rev() { + if !self.result.locals[l].ty.clone().is_copy(self.db, self.owner) { + let prev = std::mem::replace(current, self.new_basic_block()); + self.set_terminator( + prev, + TerminatorKind::Drop { place: l.into(), target: *current, unwind: None }, + MirSpan::Unknown, + ); + } + self.push_statement( + *current, + StatementKind::StorageDead(l).with_span(MirSpan::Unknown), + ); } - *cond_ty = ty.clone(); - cond_place.projection.push(ProjectionElem::Deref); } } @@ -1452,6 +1737,26 @@ fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result { (_, chalk_ir::Scalar::Float(_)) => CastKind::IntToFloat, (_, _) => CastKind::IntToInt, }, + (TyKind::Scalar(_), TyKind::Raw(..)) => CastKind::PointerFromExposedAddress, + (TyKind::Raw(..), TyKind::Scalar(_)) => CastKind::PointerExposeAddress, + (TyKind::Raw(_, a) | TyKind::Ref(_, _, a), TyKind::Raw(_, b) | TyKind::Ref(_, _, b)) => { + CastKind::Pointer(if a == b { + PointerCast::MutToConstPointer + } else if matches!(a.kind(Interner), TyKind::Slice(_) | TyKind::Str) + && matches!(b.kind(Interner), TyKind::Slice(_) | TyKind::Str) + { + // slice to slice cast is no-op (metadata is not touched), so we use this + PointerCast::MutToConstPointer + } else if matches!(b.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) { + PointerCast::Unsize + } else if matches!(a.kind(Interner), TyKind::Slice(s) if s == b) { + PointerCast::ArrayToPointer + } else { + // cast between two sized pointer, like *const i32 to *const i8. There is no specific variant + // for it in `PointerCast` so we use `MutToConstPointer` + PointerCast::MutToConstPointer + }) + } // Enum to int casts (TyKind::Scalar(_), TyKind::Adt(..)) | (TyKind::Adt(..), TyKind::Scalar(_)) => { CastKind::IntToInt @@ -1460,20 +1765,122 @@ fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result { }) } +pub fn mir_body_for_closure_query( + db: &dyn HirDatabase, + closure: ClosureId, +) -> Result> { + let (owner, expr) = db.lookup_intern_closure(closure.into()); + let body = db.body(owner); + let infer = db.infer(owner); + let Expr::Closure { args, body: root, .. } = &body[expr] else { + implementation_error!("closure expression is not closure"); + }; + let TyKind::Closure(_, substs) = &infer[expr].kind(Interner) else { + implementation_error!("closure expression is not closure"); + }; + let (captures, kind) = infer.closure_info(&closure); + let mut ctx = MirLowerCtx::new(db, owner, &body, &infer); + // 0 is return local + ctx.result.locals.alloc(Local { ty: infer[*root].clone() }); + let closure_local = ctx.result.locals.alloc(Local { + ty: match kind { + FnTrait::FnOnce => infer[expr].clone(), + FnTrait::FnMut => TyKind::Ref(Mutability::Mut, static_lifetime(), infer[expr].clone()) + .intern(Interner), + FnTrait::Fn => TyKind::Ref(Mutability::Not, static_lifetime(), infer[expr].clone()) + .intern(Interner), + }, + }); + ctx.result.param_locals.push(closure_local); + let Some(sig) = ClosureSubst(substs).sig_ty().callable_sig(db) else { + implementation_error!("closure has not callable sig"); + }; + let current = ctx.lower_params_and_bindings( + args.iter().zip(sig.params().iter()).map(|(x, y)| (*x, y.clone())), + |_| true, + )?; + if let Some(current) = ctx.lower_expr_to_place(*root, return_slot().into(), current)? { + let current = ctx.pop_drop_scope_assert_finished(current)?; + ctx.set_terminator(current, TerminatorKind::Return, (*root).into()); + } + let mut upvar_map: FxHashMap> = FxHashMap::default(); + for (i, capture) in captures.iter().enumerate() { + let local = ctx.binding_local(capture.place.local)?; + upvar_map.entry(local).or_default().push((capture, i)); + } + let mut err = None; + let closure_local = ctx.result.locals.iter().nth(1).unwrap().0; + let closure_projection = match kind { + FnTrait::FnOnce => vec![], + FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref], + }; + ctx.result.walk_places(|p| { + if let Some(x) = upvar_map.get(&p.local) { + let r = x.iter().find(|x| { + if p.projection.len() < x.0.place.projections.len() { + return false; + } + for (x, y) in p.projection.iter().zip(x.0.place.projections.iter()) { + match (x, y) { + (ProjectionElem::Deref, ProjectionElem::Deref) => (), + (ProjectionElem::Field(x), ProjectionElem::Field(y)) if x == y => (), + ( + ProjectionElem::TupleOrClosureField(x), + ProjectionElem::TupleOrClosureField(y), + ) if x == y => (), + _ => return false, + } + } + true + }); + match r { + Some(x) => { + p.local = closure_local; + let mut next_projs = closure_projection.clone(); + next_projs.push(PlaceElem::TupleOrClosureField(x.1)); + let prev_projs = mem::take(&mut p.projection); + if x.0.kind != CaptureKind::ByValue { + next_projs.push(ProjectionElem::Deref); + } + next_projs.extend(prev_projs.iter().cloned().skip(x.0.place.projections.len())); + p.projection = next_projs.into(); + } + None => err = Some(p.clone()), + } + } + }); + ctx.result.binding_locals = ctx + .result + .binding_locals + .into_iter() + .filter(|x| ctx.body[x.0].owner == Some(expr)) + .collect(); + if let Some(err) = err { + return Err(MirLowerError::UnresolvedUpvar(err)); + } + ctx.result.shrink_to_fit(); + Ok(Arc::new(ctx.result)) +} + pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result> { let _p = profile::span("mir_body_query").detail(|| match def { - DefWithBodyId::FunctionId(it) => db.function_data(it).name.to_string(), - DefWithBodyId::StaticId(it) => db.static_data(it).name.clone().to_string(), - DefWithBodyId::ConstId(it) => { - db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string() - } + DefWithBodyId::FunctionId(it) => db.function_data(it).name.display(db.upcast()).to_string(), + DefWithBodyId::StaticId(it) => db.static_data(it).name.display(db.upcast()).to_string(), + DefWithBodyId::ConstId(it) => db + .const_data(it) + .name + .clone() + .unwrap_or_else(Name::missing) + .display(db.upcast()) + .to_string(), DefWithBodyId::VariantId(it) => { - db.enum_data(it.parent).variants[it.local_id].name.to_string() + db.enum_data(it.parent).variants[it.local_id].name.display(db.upcast()).to_string() } }); let body = db.body(def); let infer = db.infer(def); - let result = lower_to_mir(db, def, &body, &infer, body.body_expr)?; + let mut result = lower_to_mir(db, def, &body, &infer, body.body_expr)?; + result.shrink_to_fit(); Ok(Arc::new(result)) } @@ -1497,85 +1904,39 @@ pub fn lower_to_mir( if let Some((_, x)) = infer.type_mismatches().next() { return Err(MirLowerError::TypeMismatch(x.clone())); } - let mut basic_blocks = Arena::new(); - let start_block = - basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false }); - let mut locals = Arena::new(); + let mut ctx = MirLowerCtx::new(db, owner, body, infer); // 0 is return local - locals.alloc(Local { ty: infer[root_expr].clone() }); - let mut binding_locals: ArenaMap = ArenaMap::new(); + ctx.result.locals.alloc(Local { ty: ctx.expr_ty_after_adjustments(root_expr) }); + let binding_picker = |b: BindingId| { + if root_expr == body.body_expr { + body[b].owner.is_none() + } else { + body[b].owner == Some(root_expr) + } + }; // 1 to param_len is for params - let param_locals: Vec = if let DefWithBodyId::FunctionId(fid) = owner { - let substs = TyBuilder::placeholder_subst(db, fid); - let callable_sig = db.callable_item_signature(fid.into()).substitute(Interner, &substs); - body.params - .iter() - .zip(callable_sig.params().iter()) - .map(|(&x, ty)| { - let local_id = locals.alloc(Local { ty: ty.clone() }); - if let Pat::Bind { id, subpat: None } = body[x] { - if matches!( - body.bindings[id].mode, - BindingAnnotation::Unannotated | BindingAnnotation::Mutable - ) { - binding_locals.insert(id, local_id); - } - } - local_id - }) - .collect() - } else { - if !body.params.is_empty() { - return Err(MirLowerError::TypeError("Unexpected parameter for non function body")); - } - vec![] - }; - // and then rest of bindings - for (id, _) in body.bindings.iter() { - if !binding_locals.contains_idx(id) { - binding_locals.insert(id, locals.alloc(Local { ty: infer[id].clone() })); - } - } - let mir = MirBody { - basic_blocks, - locals, - start_block, - binding_locals, - param_locals, - owner, - arg_count: body.params.len(), - }; - let mut ctx = MirLowerCtx { - result: mir, - db, - infer, - body, - owner, - current_loop_blocks: None, - discr_temp: None, - }; - let mut current = start_block; - for (¶m, local) in body.params.iter().zip(ctx.result.param_locals.clone().into_iter()) { - if let Pat::Bind { id, .. } = body[param] { - if local == ctx.result.binding_locals[id] { - continue; + // FIXME: replace with let chain once it becomes stable + let current = 'b: { + if body.body_expr == root_expr { + // otherwise it's an inline const, and has no parameter + if let DefWithBodyId::FunctionId(fid) = owner { + let substs = TyBuilder::placeholder_subst(db, fid); + let callable_sig = + db.callable_item_signature(fid.into()).substitute(Interner, &substs); + break 'b ctx.lower_params_and_bindings( + body.params + .iter() + .zip(callable_sig.params().iter()) + .map(|(x, y)| (*x, y.clone())), + binding_picker, + )?; } } - let r = ctx.pattern_match( - current, - None, - local.into(), - ctx.result.locals[local].ty.clone(), - param, - BindingAnnotation::Unannotated, - )?; - if let Some(b) = r.1 { - ctx.set_terminator(b, Terminator::Unreachable); - } - current = r.0; - } - if let Some(b) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? { - ctx.result.basic_blocks[b].terminator = Some(Terminator::Return); + ctx.lower_params_and_bindings([].into_iter(), binding_picker)? + }; + if let Some(current) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? { + let current = ctx.pop_drop_scope_assert_finished(current)?; + ctx.set_terminator(current, TerminatorKind::Return, root_expr.into()); } Ok(ctx.result) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs index fe8147dcd3e7..d2c8d9a089e0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs @@ -1,6 +1,7 @@ //! MIR lowering for places use super::*; +use hir_def::{lang_item::lang_attr, FunctionId}; use hir_expand::name; macro_rules! not_supported { @@ -15,8 +16,8 @@ fn lower_expr_to_some_place_without_adjust( expr_id: ExprId, prev_block: BasicBlockId, ) -> Result> { - let ty = self.expr_ty(expr_id); - let place = self.temp(ty)?; + let ty = self.expr_ty_without_adjust(expr_id); + let place = self.temp(ty, prev_block, expr_id.into())?; let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)? else { return Ok(None); }; @@ -29,9 +30,11 @@ fn lower_expr_to_some_place_with_adjust( prev_block: BasicBlockId, adjustments: &[Adjustment], ) -> Result> { - let ty = - adjustments.last().map(|x| x.target.clone()).unwrap_or_else(|| self.expr_ty(expr_id)); - let place = self.temp(ty)?; + let ty = adjustments + .last() + .map(|x| x.target.clone()) + .unwrap_or_else(|| self.expr_ty_without_adjust(expr_id)); + let place = self.temp(ty, prev_block, expr_id.into())?; let Some(current) = self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)? else { return Ok(None); }; @@ -62,7 +65,7 @@ pub(super) fn lower_expr_as_place_with_adjust( )? else { return Ok(None); }; - x.0.projection.push(ProjectionElem::Deref); + x.0 = x.0.project(ProjectionElem::Deref); Ok(Some(x)) } Adjust::Deref(Some(od)) => { @@ -79,7 +82,7 @@ pub(super) fn lower_expr_as_place_with_adjust( r, rest.last() .map(|x| x.target.clone()) - .unwrap_or_else(|| self.expr_ty(expr_id)), + .unwrap_or_else(|| self.expr_ty_without_adjust(expr_id)), last.target.clone(), expr_id.into(), match od.0 { @@ -125,35 +128,74 @@ pub(super) fn lower_expr_as_place_without_adjust( match &self.body.exprs[expr_id] { Expr::Path(p) => { let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); - let Some(pr) = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) else { - return Err(MirLowerError::unresolved_path(self.db, p)); - }; - let pr = match pr { - ResolveValueResult::ValueNs(v) => v, - ResolveValueResult::Partial(..) => return try_rvalue(self), + let Some(pr) = resolver.resolve_path_in_value_ns_fully(self.db.upcast(), p) else { + return try_rvalue(self); }; match pr { ValueNs::LocalBinding(pat_id) => { - Ok(Some((self.result.binding_locals[pat_id].into(), current))) + Ok(Some((self.binding_local(pat_id)?.into(), current))) + } + ValueNs::StaticId(s) => { + let ty = self.expr_ty_without_adjust(expr_id); + let ref_ty = + TyKind::Ref(Mutability::Not, static_lifetime(), ty).intern(Interner); + let temp: Place = self.temp(ref_ty, current, expr_id.into())?.into(); + self.push_assignment( + current, + temp.clone(), + Operand::Static(s).into(), + expr_id.into(), + ); + Ok(Some((temp.project(ProjectionElem::Deref), current))) } _ => try_rvalue(self), } } Expr::UnaryOp { expr, op } => match op { - hir_def::expr::UnaryOp::Deref => { - if !matches!( - self.expr_ty(*expr).kind(Interner), - TyKind::Ref(..) | TyKind::Raw(..) - ) { - let Some(_) = self.lower_expr_as_place(current, *expr, true)? else { + hir_def::hir::UnaryOp::Deref => { + let is_builtin = match self.expr_ty_without_adjust(*expr).kind(Interner) { + TyKind::Ref(..) | TyKind::Raw(..) => true, + TyKind::Adt(id, _) => { + if let Some(lang_item) = lang_attr(self.db.upcast(), id.0) { + lang_item == LangItem::OwnedBox + } else { + false + } + } + _ => false, + }; + if !is_builtin { + let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else { return Ok(None); }; - not_supported!("explicit overloaded deref"); + return self.lower_overloaded_deref( + current, + p, + self.expr_ty_after_adjustments(*expr), + self.expr_ty_without_adjust(expr_id), + expr_id.into(), + 'b: { + if let Some((f, _)) = self.infer.method_resolution(expr_id) { + if let Some(deref_trait) = + self.resolve_lang_item(LangItem::DerefMut)?.as_trait() + { + if let Some(deref_fn) = self + .db + .trait_data(deref_trait) + .method_by_name(&name![deref_mut]) + { + break 'b deref_fn == f; + } + } + } + false + }, + ); } let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else { return Ok(None); }; - r.projection.push(ProjectionElem::Deref); + r = r.project(ProjectionElem::Deref); Ok(Some((r, current))) } _ => try_rvalue(self), @@ -169,25 +211,84 @@ pub(super) fn lower_expr_as_place_without_adjust( let base_ty = self.expr_ty_after_adjustments(*base); let index_ty = self.expr_ty_after_adjustments(*index); if index_ty != TyBuilder::usize() - || !matches!(base_ty.kind(Interner), TyKind::Array(..) | TyKind::Slice(..)) + || !matches!( + base_ty.strip_reference().kind(Interner), + TyKind::Array(..) | TyKind::Slice(..) + ) { - not_supported!("overloaded index"); + let Some(index_fn) = self.infer.method_resolution(expr_id) else { + return Err(MirLowerError::UnresolvedMethod("[overloaded index]".to_string())); + }; + let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else { + return Ok(None); + }; + let Some((index_operand, current)) = self.lower_expr_to_some_operand(*index, current)? else { + return Ok(None); + }; + return self.lower_overloaded_index( + current, + base_place, + base_ty, + self.expr_ty_without_adjust(expr_id), + index_operand, + expr_id.into(), + index_fn, + ); } + let adjusts = self + .infer + .expr_adjustments + .get(base) + .and_then(|x| x.split_last()) + .map(|x| x.1) + .unwrap_or(&[]); let Some((mut p_base, current)) = - self.lower_expr_as_place(current, *base, true)? else { + self.lower_expr_as_place_with_adjust(current, *base, true, adjusts)? + else { return Ok(None); }; - let l_index = self.temp(self.expr_ty_after_adjustments(*index))?; + let l_index = + self.temp(self.expr_ty_after_adjustments(*index), current, expr_id.into())?; let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)? else { return Ok(None); }; - p_base.projection.push(ProjectionElem::Index(l_index)); + p_base = p_base.project(ProjectionElem::Index(l_index)); Ok(Some((p_base, current))) } _ => try_rvalue(self), } } + fn lower_overloaded_index( + &mut self, + current: BasicBlockId, + place: Place, + base_ty: Ty, + result_ty: Ty, + index_operand: Operand, + span: MirSpan, + index_fn: (FunctionId, Substitution), + ) -> Result> { + let mutability = match base_ty.as_reference() { + Some((_, _, mutability)) => mutability, + None => Mutability::Not, + }; + let result_ref = TyKind::Ref(mutability, static_lifetime(), result_ty).intern(Interner); + let mut result: Place = self.temp(result_ref, current, span)?.into(); + let index_fn_op = Operand::const_zst( + TyKind::FnDef( + self.db.intern_callable_def(CallableDefId::FunctionId(index_fn.0)).into(), + index_fn.1, + ) + .intern(Interner), + ); + let Some(current) = self.lower_call(index_fn_op, Box::new([Operand::Copy(place), index_operand]), result.clone(), current, false, span)? else { + return Ok(None); + }; + result = result.project(ProjectionElem::Deref); + Ok(Some((result, current))) + } + fn lower_overloaded_deref( &mut self, current: BasicBlockId, @@ -209,7 +310,7 @@ fn lower_overloaded_deref( }; let ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), source_ty.clone()).intern(Interner); let target_ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), target_ty).intern(Interner); - let ref_place: Place = self.temp(ty_ref)?.into(); + let ref_place: Place = self.temp(ty_ref, current, span)?.into(); self.push_assignment(current, ref_place.clone(), Rvalue::Ref(borrow_kind, place), span); let deref_trait = self .resolve_lang_item(trait_lang_item)? @@ -227,11 +328,11 @@ fn lower_overloaded_deref( ) .intern(Interner), ); - let mut result: Place = self.temp(target_ty_ref)?.into(); - let Some(current) = self.lower_call(deref_fn_op, vec![Operand::Copy(ref_place)], result.clone(), current, false)? else { + let mut result: Place = self.temp(target_ty_ref, current, span)?.into(); + let Some(current) = self.lower_call(deref_fn_op, Box::new([Operand::Copy(ref_place)]), result.clone(), current, false, span)? else { return Ok(None); }; - result.projection.push(ProjectionElem::Deref); + result = result.project(ProjectionElem::Deref); Ok(Some((result, current))) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs new file mode 100644 index 000000000000..ff43c64a9e60 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -0,0 +1,617 @@ +//! MIR lowering for patterns + +use hir_def::{hir::LiteralOrConst, resolver::HasResolver, AssocItemId}; + +use crate::BindingMode; + +use super::*; + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirLowerError::NotSupported(format!($x))) + }; +} + +pub(super) enum AdtPatternShape<'a> { + Tuple { args: &'a [PatId], ellipsis: Option }, + Record { args: &'a [RecordFieldPat] }, + Unit, +} + +/// We need to do pattern matching in two phases: One to check if the pattern matches, and one to fill the bindings +/// of patterns. This is necessary to prevent double moves and similar problems. For example: +/// ```ignore +/// struct X; +/// match (X, 3) { +/// (b, 2) | (b, 3) => {}, +/// _ => {} +/// } +/// ``` +/// If we do everything in one pass, we will move `X` to the first `b`, then we see that the second field of tuple +/// doesn't match and we should move the `X` to the second `b` (which here is the same thing, but doesn't need to be) and +/// it might even doesn't match the second pattern and we may want to not move `X` at all. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MatchingMode { + /// Check that if this pattern matches + Check, + /// Assume that this pattern matches, fill bindings + Bind, +} + +impl MirLowerCtx<'_> { + /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if + /// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which + /// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the + /// mismatched path block is `None`. + /// + /// By default, it will create a new block for mismatched path. If you already have one, you can provide it with + /// `current_else` argument to save an unnecessary jump. If `current_else` isn't `None`, the result mismatched path + /// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block, + /// so it should be an empty block. + pub(super) fn pattern_match( + &mut self, + current: BasicBlockId, + current_else: Option, + cond_place: Place, + pattern: PatId, + ) -> Result<(BasicBlockId, Option)> { + let (current, current_else) = self.pattern_match_inner( + current, + current_else, + cond_place.clone(), + pattern, + MatchingMode::Check, + )?; + let (current, current_else) = self.pattern_match_inner( + current, + current_else, + cond_place, + pattern, + MatchingMode::Bind, + )?; + Ok((current, current_else)) + } + + fn pattern_match_inner( + &mut self, + mut current: BasicBlockId, + mut current_else: Option, + mut cond_place: Place, + pattern: PatId, + mode: MatchingMode, + ) -> Result<(BasicBlockId, Option)> { + let cnt = self.infer.pat_adjustments.get(&pattern).map(|x| x.len()).unwrap_or_default(); + cond_place.projection = cond_place + .projection + .iter() + .cloned() + .chain((0..cnt).map(|_| ProjectionElem::Deref)) + .collect::>() + .into(); + Ok(match &self.body.pats[pattern] { + Pat::Missing => return Err(MirLowerError::IncompletePattern), + Pat::Wild => (current, current_else), + Pat::Tuple { args, ellipsis } => { + let subst = match self.infer[pattern].kind(Interner) { + TyKind::Tuple(_, s) => s, + _ => { + return Err(MirLowerError::TypeError( + "non tuple type matched with tuple pattern", + )) + } + }; + self.pattern_match_tuple_like( + current, + current_else, + args, + *ellipsis, + (0..subst.len(Interner)).map(|i| PlaceElem::TupleOrClosureField(i)), + &(&mut cond_place), + mode, + )? + } + Pat::Or(pats) => { + let then_target = self.new_basic_block(); + let mut finished = false; + for pat in &**pats { + let (mut next, next_else) = self.pattern_match_inner( + current, + None, + (&mut cond_place).clone(), + *pat, + MatchingMode::Check, + )?; + if mode == MatchingMode::Bind { + (next, _) = self.pattern_match_inner( + next, + None, + (&mut cond_place).clone(), + *pat, + MatchingMode::Bind, + )?; + } + self.set_goto(next, then_target, pattern.into()); + match next_else { + Some(t) => { + current = t; + } + None => { + finished = true; + break; + } + } + } + if !finished { + if mode == MatchingMode::Bind { + self.set_terminator(current, TerminatorKind::Unreachable, pattern.into()); + } else { + let ce = *current_else.get_or_insert_with(|| self.new_basic_block()); + self.set_goto(current, ce, pattern.into()); + } + } + (then_target, current_else) + } + Pat::Record { args, .. } => { + let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { + not_supported!("unresolved variant for record"); + }; + self.pattern_matching_variant( + cond_place, + variant, + current, + pattern.into(), + current_else, + AdtPatternShape::Record { args: &*args }, + mode, + )? + } + Pat::Range { start, end } => { + let mut add_check = |l: &LiteralOrConst, binop| -> Result<()> { + let lv = + self.lower_literal_or_const_to_operand(self.infer[pattern].clone(), l)?; + let else_target = *current_else.get_or_insert_with(|| self.new_basic_block()); + let next = self.new_basic_block(); + let discr: Place = + self.temp(TyBuilder::bool(), current, pattern.into())?.into(); + self.push_assignment( + current, + discr.clone(), + Rvalue::CheckedBinaryOp( + binop, + lv, + Operand::Copy((&mut cond_place).clone()), + ), + pattern.into(), + ); + let discr = Operand::Copy(discr); + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, next, else_target), + }, + pattern.into(), + ); + current = next; + Ok(()) + }; + if mode == MatchingMode::Check { + if let Some(start) = start { + add_check(start, BinOp::Le)?; + } + if let Some(end) = end { + add_check(end, BinOp::Ge)?; + } + } + (current, current_else) + } + Pat::Slice { prefix, slice, suffix } => { + if mode == MatchingMode::Check { + // emit runtime length check for slice + if let TyKind::Slice(_) = self.infer[pattern].kind(Interner) { + let pattern_len = prefix.len() + suffix.len(); + let place_len: Place = + self.temp(TyBuilder::usize(), current, pattern.into())?.into(); + self.push_assignment( + current, + place_len.clone(), + Rvalue::Len((&mut cond_place).clone()), + pattern.into(), + ); + let else_target = + *current_else.get_or_insert_with(|| self.new_basic_block()); + let next = self.new_basic_block(); + if slice.is_none() { + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr: Operand::Copy(place_len), + targets: SwitchTargets::static_if( + pattern_len as u128, + next, + else_target, + ), + }, + pattern.into(), + ); + } else { + let c = Operand::from_concrete_const( + pattern_len.to_le_bytes().to_vec(), + MemoryMap::default(), + TyBuilder::usize(), + ); + let discr: Place = + self.temp(TyBuilder::bool(), current, pattern.into())?.into(); + self.push_assignment( + current, + discr.clone(), + Rvalue::CheckedBinaryOp(BinOp::Le, c, Operand::Copy(place_len)), + pattern.into(), + ); + let discr = Operand::Copy(discr); + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, next, else_target), + }, + pattern.into(), + ); + } + current = next; + } + } + for (i, &pat) in prefix.iter().enumerate() { + let next_place = (&mut cond_place).project(ProjectionElem::ConstantIndex { + offset: i as u64, + from_end: false, + }); + (current, current_else) = + self.pattern_match_inner(current, current_else, next_place, pat, mode)?; + } + if let Some(slice) = slice { + if mode == MatchingMode::Bind { + if let Pat::Bind { id, subpat: _ } = self.body[*slice] { + let next_place = (&mut cond_place).project(ProjectionElem::Subslice { + from: prefix.len() as u64, + to: suffix.len() as u64, + }); + (current, current_else) = self.pattern_match_binding( + id, + next_place, + (*slice).into(), + current, + current_else, + )?; + } + } + } + for (i, &pat) in suffix.iter().enumerate() { + let next_place = (&mut cond_place).project(ProjectionElem::ConstantIndex { + offset: i as u64, + from_end: true, + }); + (current, current_else) = + self.pattern_match_inner(current, current_else, next_place, pat, mode)?; + } + (current, current_else) + } + Pat::Path(p) => match self.infer.variant_resolution_for_pat(pattern) { + Some(variant) => self.pattern_matching_variant( + cond_place, + variant, + current, + pattern.into(), + current_else, + AdtPatternShape::Unit, + mode, + )?, + None => { + let unresolved_name = || MirLowerError::unresolved_path(self.db, p); + let resolver = self.owner.resolver(self.db.upcast()); + let pr = resolver + .resolve_path_in_value_ns(self.db.upcast(), p) + .ok_or_else(unresolved_name)?; + let (c, subst) = 'b: { + if let Some(x) = self.infer.assoc_resolutions_for_pat(pattern) { + if let AssocItemId::ConstId(c) = x.0 { + break 'b (c, x.1); + } + } + if let ResolveValueResult::ValueNs(v) = pr { + if let ValueNs::ConstId(c) = v { + break 'b (c, Substitution::empty(Interner)); + } + } + not_supported!("path in pattern position that is not const or variant") + }; + let tmp: Place = + self.temp(self.infer[pattern].clone(), current, pattern.into())?.into(); + let span = pattern.into(); + self.lower_const( + c.into(), + current, + tmp.clone(), + subst, + span, + self.infer[pattern].clone(), + )?; + let tmp2: Place = self.temp(TyBuilder::bool(), current, pattern.into())?.into(); + self.push_assignment( + current, + tmp2.clone(), + Rvalue::CheckedBinaryOp( + BinOp::Eq, + Operand::Copy(tmp), + Operand::Copy(cond_place), + ), + span, + ); + let next = self.new_basic_block(); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr: Operand::Copy(tmp2), + targets: SwitchTargets::static_if(1, next, else_target), + }, + span, + ); + (next, Some(else_target)) + } + }, + Pat::Lit(l) => match &self.body.exprs[*l] { + Expr::Literal(l) => { + let c = self.lower_literal_to_operand(self.infer[pattern].clone(), l)?; + if mode == MatchingMode::Check { + self.pattern_match_const(current_else, current, c, cond_place, pattern)? + } else { + (current, current_else) + } + } + _ => not_supported!("expression path literal"), + }, + Pat::Bind { id, subpat } => { + if let Some(subpat) = subpat { + (current, current_else) = self.pattern_match_inner( + current, + current_else, + (&mut cond_place).clone(), + *subpat, + mode, + )? + } + if mode == MatchingMode::Bind { + self.pattern_match_binding( + *id, + cond_place, + pattern.into(), + current, + current_else, + )? + } else { + (current, current_else) + } + } + Pat::TupleStruct { path: _, args, ellipsis } => { + let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { + not_supported!("unresolved variant"); + }; + self.pattern_matching_variant( + cond_place, + variant, + current, + pattern.into(), + current_else, + AdtPatternShape::Tuple { args, ellipsis: *ellipsis }, + mode, + )? + } + Pat::Ref { pat, mutability: _ } => self.pattern_match_inner( + current, + current_else, + cond_place.project(ProjectionElem::Deref), + *pat, + mode, + )?, + Pat::Box { .. } => not_supported!("box pattern"), + Pat::ConstBlock(_) => not_supported!("const block pattern"), + }) + } + + fn pattern_match_binding( + &mut self, + id: BindingId, + cond_place: Place, + span: MirSpan, + current: BasicBlockId, + current_else: Option, + ) -> Result<(BasicBlockId, Option)> { + let target_place = self.binding_local(id)?; + let mode = self.infer.binding_modes[id]; + self.push_storage_live(id, current)?; + self.push_assignment( + current, + target_place.into(), + match mode { + BindingMode::Move => Operand::Copy(cond_place).into(), + BindingMode::Ref(Mutability::Not) => Rvalue::Ref(BorrowKind::Shared, cond_place), + BindingMode::Ref(Mutability::Mut) => { + Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, cond_place) + } + }, + span, + ); + Ok((current, current_else)) + } + + fn pattern_match_const( + &mut self, + current_else: Option, + current: BasicBlockId, + c: Operand, + cond_place: Place, + pattern: Idx, + ) -> Result<(BasicBlockId, Option)> { + let then_target = self.new_basic_block(); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + let discr: Place = self.temp(TyBuilder::bool(), current, pattern.into())?.into(); + self.push_assignment( + current, + discr.clone(), + Rvalue::CheckedBinaryOp(BinOp::Eq, c, Operand::Copy(cond_place)), + pattern.into(), + ); + let discr = Operand::Copy(discr); + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, then_target, else_target), + }, + pattern.into(), + ); + Ok((then_target, Some(else_target))) + } + + fn pattern_matching_variant( + &mut self, + cond_place: Place, + variant: VariantId, + mut current: BasicBlockId, + span: MirSpan, + mut current_else: Option, + shape: AdtPatternShape<'_>, + mode: MatchingMode, + ) -> Result<(BasicBlockId, Option)> { + Ok(match variant { + VariantId::EnumVariantId(v) => { + if mode == MatchingMode::Check { + let e = self.const_eval_discriminant(v)? as u128; + let tmp = self.discr_temp_place(current); + self.push_assignment( + current, + tmp.clone(), + Rvalue::Discriminant(cond_place.clone()), + span, + ); + let next = self.new_basic_block(); + let else_target = current_else.get_or_insert_with(|| self.new_basic_block()); + self.set_terminator( + current, + TerminatorKind::SwitchInt { + discr: Operand::Copy(tmp), + targets: SwitchTargets::static_if(e, next, *else_target), + }, + span, + ); + current = next; + } + let enum_data = self.db.enum_data(v.parent); + self.pattern_matching_variant_fields( + shape, + &enum_data.variants[v.local_id].variant_data, + variant, + current, + current_else, + &cond_place, + mode, + )? + } + VariantId::StructId(s) => { + let struct_data = self.db.struct_data(s); + self.pattern_matching_variant_fields( + shape, + &struct_data.variant_data, + variant, + current, + current_else, + &cond_place, + mode, + )? + } + VariantId::UnionId(_) => { + return Err(MirLowerError::TypeError("pattern matching on union")) + } + }) + } + + fn pattern_matching_variant_fields( + &mut self, + shape: AdtPatternShape<'_>, + variant_data: &VariantData, + v: VariantId, + current: BasicBlockId, + current_else: Option, + cond_place: &Place, + mode: MatchingMode, + ) -> Result<(BasicBlockId, Option)> { + Ok(match shape { + AdtPatternShape::Record { args } => { + let it = args + .iter() + .map(|x| { + let field_id = + variant_data.field(&x.name).ok_or(MirLowerError::UnresolvedField)?; + Ok(( + PlaceElem::Field(FieldId { parent: v.into(), local_id: field_id }), + x.pat, + )) + }) + .collect::>>()?; + self.pattern_match_adt(current, current_else, it.into_iter(), cond_place, mode)? + } + AdtPatternShape::Tuple { args, ellipsis } => { + let fields = variant_data + .fields() + .iter() + .map(|(x, _)| PlaceElem::Field(FieldId { parent: v.into(), local_id: x })); + self.pattern_match_tuple_like( + current, + current_else, + args, + ellipsis, + fields, + cond_place, + mode, + )? + } + AdtPatternShape::Unit => (current, current_else), + }) + } + + fn pattern_match_adt( + &mut self, + mut current: BasicBlockId, + mut current_else: Option, + args: impl Iterator, + cond_place: &Place, + mode: MatchingMode, + ) -> Result<(BasicBlockId, Option)> { + for (proj, arg) in args { + let cond_place = cond_place.project(proj); + (current, current_else) = + self.pattern_match_inner(current, current_else, cond_place, arg, mode)?; + } + Ok((current, current_else)) + } + + fn pattern_match_tuple_like( + &mut self, + current: BasicBlockId, + current_else: Option, + args: &[PatId], + ellipsis: Option, + fields: impl DoubleEndedIterator + Clone, + cond_place: &Place, + mode: MatchingMode, + ) -> Result<(BasicBlockId, Option)> { + let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len())); + let it = al + .iter() + .zip(fields.clone()) + .chain(ar.iter().rev().zip(fields.rev())) + .map(|(x, y)| (y, *x)); + self.pattern_match_adt(current, current_else, it, cond_place, mode) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs new file mode 100644 index 000000000000..ce3f7a8e5102 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs @@ -0,0 +1,351 @@ +//! Monomorphization of mir, which is used in mir interpreter and const eval. +//! +//! The job of monomorphization is: +//! * Monomorphization. That is, replacing `Option` with `Option` where `T:=i32` substitution +//! is provided +//! * Normalizing types, for example replacing RPIT of other functions called in this body. +//! +//! So the monomorphization should be called even if the substitution is empty. + +use std::mem; + +use chalk_ir::{ + fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable}, + ConstData, DebruijnIndex, +}; +use hir_def::{DefWithBodyId, GeneralConstId}; +use triomphe::Arc; + +use crate::{ + consteval::unknown_const, + db::HirDatabase, + from_placeholder_idx, + infer::normalize, + method_resolution::lookup_impl_const, + utils::{generics, Generics}, + ClosureId, Const, Interner, ProjectionTy, Substitution, TraitEnvironment, Ty, TyKind, +}; + +use super::{MirBody, MirLowerError, Operand, Rvalue, StatementKind, TerminatorKind}; + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirLowerError::NotSupported(format!($x))) + }; +} + +struct Filler<'a> { + db: &'a dyn HirDatabase, + trait_env: Arc, + subst: &'a Substitution, + generics: Option, + owner: DefWithBodyId, +} +impl FallibleTypeFolder for Filler<'_> { + type Error = MirLowerError; + + fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn try_fold_ty( + &mut self, + ty: Ty, + outer_binder: DebruijnIndex, + ) -> std::result::Result { + match ty.kind(Interner) { + TyKind::AssociatedType(id, subst) => { + // I don't know exactly if and why this is needed, but it looks like `normalize_ty` likes + // this kind of associated types. + Ok(TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy { + associated_ty_id: *id, + substitution: subst.clone().try_fold_with(self, outer_binder)?, + })) + .intern(Interner)) + } + TyKind::OpaqueType(id, subst) => { + let impl_trait_id = self.db.lookup_intern_impl_trait_id((*id).into()); + let subst = subst.clone().try_fold_with(self.as_dyn(), outer_binder)?; + match impl_trait_id { + crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { + let infer = self.db.infer(func.into()); + let filler = &mut Filler { + db: self.db, + owner: self.owner, + trait_env: self.trait_env.clone(), + subst: &subst, + generics: Some(generics(self.db.upcast(), func.into())), + }; + filler.try_fold_ty(infer.type_of_rpit[idx].clone(), outer_binder) + } + crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { + not_supported!("async block impl trait"); + } + } + } + _ => ty.try_super_fold_with(self.as_dyn(), outer_binder), + } + } + + fn try_fold_free_placeholder_const( + &mut self, + _ty: chalk_ir::Ty, + idx: chalk_ir::PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> std::result::Result, Self::Error> { + let x = from_placeholder_idx(self.db, idx); + let Some(idx) = self.generics.as_ref().and_then(|g| g.param_idx(x)) else { + not_supported!("missing idx in generics"); + }; + Ok(self + .subst + .as_slice(Interner) + .get(idx) + .and_then(|x| x.constant(Interner)) + .ok_or_else(|| MirLowerError::GenericArgNotProvided(x, self.subst.clone()))? + .clone()) + } + + fn try_fold_free_placeholder_ty( + &mut self, + idx: chalk_ir::PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> std::result::Result { + let x = from_placeholder_idx(self.db, idx); + let Some(idx) = self.generics.as_ref().and_then(|g| g.param_idx(x)) else { + not_supported!("missing idx in generics"); + }; + Ok(self + .subst + .as_slice(Interner) + .get(idx) + .and_then(|x| x.ty(Interner)) + .ok_or_else(|| MirLowerError::GenericArgNotProvided(x, self.subst.clone()))? + .clone()) + } + + fn try_fold_const( + &mut self, + constant: chalk_ir::Const, + outer_binder: DebruijnIndex, + ) -> Result, Self::Error> { + let next_ty = normalize( + self.db, + self.trait_env.clone(), + constant.data(Interner).ty.clone().try_fold_with(self, outer_binder)?, + ); + ConstData { ty: next_ty, value: constant.data(Interner).value.clone() } + .intern(Interner) + .try_super_fold_with(self, outer_binder) + } +} + +impl Filler<'_> { + fn fill_ty(&mut self, ty: &mut Ty) -> Result<(), MirLowerError> { + let tmp = mem::replace(ty, TyKind::Error.intern(Interner)); + *ty = normalize( + self.db, + self.trait_env.clone(), + tmp.try_fold_with(self, DebruijnIndex::INNERMOST)?, + ); + Ok(()) + } + + fn fill_const(&mut self, c: &mut Const) -> Result<(), MirLowerError> { + let tmp = mem::replace(c, unknown_const(c.data(Interner).ty.clone())); + *c = tmp.try_fold_with(self, DebruijnIndex::INNERMOST)?; + Ok(()) + } + + fn fill_subst(&mut self, ty: &mut Substitution) -> Result<(), MirLowerError> { + let tmp = mem::replace(ty, Substitution::empty(Interner)); + *ty = tmp.try_fold_with(self, DebruijnIndex::INNERMOST)?; + Ok(()) + } + + fn fill_operand(&mut self, op: &mut Operand) -> Result<(), MirLowerError> { + match op { + Operand::Constant(c) => { + match &c.data(Interner).value { + chalk_ir::ConstValue::BoundVar(b) => { + let resolved = self + .subst + .as_slice(Interner) + .get(b.index) + .ok_or_else(|| { + MirLowerError::GenericArgNotProvided( + self.generics + .as_ref() + .and_then(|x| x.iter().nth(b.index)) + .unwrap() + .0, + self.subst.clone(), + ) + })? + .assert_const_ref(Interner); + *c = resolved.clone(); + } + chalk_ir::ConstValue::InferenceVar(_) + | chalk_ir::ConstValue::Placeholder(_) => {} + chalk_ir::ConstValue::Concrete(cc) => match &cc.interned { + crate::ConstScalar::UnevaluatedConst(const_id, subst) => { + let mut const_id = *const_id; + let mut subst = subst.clone(); + self.fill_subst(&mut subst)?; + if let GeneralConstId::ConstId(c) = const_id { + let (c, s) = lookup_impl_const( + self.db, + self.db.trait_environment_for_body(self.owner), + c, + subst, + ); + const_id = GeneralConstId::ConstId(c); + subst = s; + } + let result = + self.db.const_eval(const_id.into(), subst).map_err(|e| { + let name = const_id.name(self.db.upcast()); + MirLowerError::ConstEvalError(name, Box::new(e)) + })?; + *c = result; + } + crate::ConstScalar::Bytes(_, _) | crate::ConstScalar::Unknown => (), + }, + } + self.fill_const(c)?; + } + Operand::Copy(_) | Operand::Move(_) | Operand::Static(_) => (), + } + Ok(()) + } + + fn fill_body(&mut self, body: &mut MirBody) -> Result<(), MirLowerError> { + for (_, l) in body.locals.iter_mut() { + self.fill_ty(&mut l.ty)?; + } + for (_, bb) in body.basic_blocks.iter_mut() { + for statement in &mut bb.statements { + match &mut statement.kind { + StatementKind::Assign(_, r) => match r { + Rvalue::Aggregate(ak, ops) => { + for op in &mut **ops { + self.fill_operand(op)?; + } + match ak { + super::AggregateKind::Array(ty) + | super::AggregateKind::Tuple(ty) + | super::AggregateKind::Closure(ty) => self.fill_ty(ty)?, + super::AggregateKind::Adt(_, subst) => self.fill_subst(subst)?, + super::AggregateKind::Union(_, _) => (), + } + } + Rvalue::ShallowInitBox(_, ty) | Rvalue::ShallowInitBoxWithAlloc(ty) => { + self.fill_ty(ty)?; + } + Rvalue::Use(op) => { + self.fill_operand(op)?; + } + Rvalue::Repeat(op, len) => { + self.fill_operand(op)?; + self.fill_const(len)?; + } + Rvalue::Ref(_, _) + | Rvalue::Len(_) + | Rvalue::Cast(_, _, _) + | Rvalue::CheckedBinaryOp(_, _, _) + | Rvalue::UnaryOp(_, _) + | Rvalue::Discriminant(_) + | Rvalue::CopyForDeref(_) => (), + }, + StatementKind::Deinit(_) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Nop => (), + } + } + if let Some(terminator) = &mut bb.terminator { + match &mut terminator.kind { + TerminatorKind::Call { func, args, .. } => { + self.fill_operand(func)?; + for op in &mut **args { + self.fill_operand(op)?; + } + } + TerminatorKind::SwitchInt { discr, .. } => { + self.fill_operand(discr)?; + } + TerminatorKind::Goto { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Drop { .. } + | TerminatorKind::DropAndReplace { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::GeneratorDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } => (), + } + } + } + Ok(()) + } +} + +pub fn monomorphized_mir_body_query( + db: &dyn HirDatabase, + owner: DefWithBodyId, + subst: Substitution, + trait_env: Arc, +) -> Result, MirLowerError> { + let generics = owner.as_generic_def_id().map(|g_def| generics(db.upcast(), g_def)); + let filler = &mut Filler { db, subst: &subst, trait_env, generics, owner }; + let body = db.mir_body(owner)?; + let mut body = (*body).clone(); + filler.fill_body(&mut body)?; + Ok(Arc::new(body)) +} + +pub fn monomorphized_mir_body_recover( + _: &dyn HirDatabase, + _: &[String], + _: &DefWithBodyId, + _: &Substitution, + _: &Arc, +) -> Result, MirLowerError> { + return Err(MirLowerError::Loop); +} + +pub fn monomorphized_mir_body_for_closure_query( + db: &dyn HirDatabase, + closure: ClosureId, + subst: Substitution, + trait_env: Arc, +) -> Result, MirLowerError> { + let (owner, _) = db.lookup_intern_closure(closure.into()); + let generics = owner.as_generic_def_id().map(|g_def| generics(db.upcast(), g_def)); + let filler = &mut Filler { db, subst: &subst, trait_env, generics, owner }; + let body = db.mir_body_for_closure(closure)?; + let mut body = (*body).clone(); + filler.fill_body(&mut body)?; + Ok(Arc::new(body)) +} + +// FIXME: remove this function. Monomorphization is a time consuming job and should always be a query. +pub fn monomorphize_mir_body_bad( + db: &dyn HirDatabase, + mut body: MirBody, + subst: Substitution, + trait_env: Arc, +) -> Result { + let owner = body.owner; + let generics = owner.as_generic_def_id().map(|g_def| generics(db.upcast(), g_def)); + let filler = &mut Filler { db, subst: &subst, trait_env, generics, owner }; + filler.fill_body(&mut body)?; + Ok(body) +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs index ffc08b7e346c..58662b01b99b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs @@ -1,39 +1,25 @@ //! A pretty-printer for MIR. -use std::fmt::{Display, Write}; +use std::{ + fmt::{Debug, Display, Write}, + mem, +}; -use hir_def::{body::Body, expr::BindingId}; +use hir_def::{body::Body, hir::BindingId}; use hir_expand::name::Name; use la_arena::ArenaMap; use crate::{ db::HirDatabase, - display::HirDisplay, - mir::{PlaceElem, ProjectionElem, StatementKind, Terminator}, + display::{ClosureStyle, HirDisplay}, + mir::{PlaceElem, ProjectionElem, StatementKind, TerminatorKind}, + ClosureId, }; use super::{ AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, Operand, Place, Rvalue, UnOp, }; -impl MirBody { - pub fn pretty_print(&self, db: &dyn HirDatabase) -> String { - let hir_body = db.body(self.owner); - let mut ctx = MirPrettyCtx::new(self, &hir_body, db); - ctx.for_body(); - ctx.result - } -} - -struct MirPrettyCtx<'a> { - body: &'a MirBody, - hir_body: &'a Body, - db: &'a dyn HirDatabase, - result: String, - ident: String, - local_to_binding: ArenaMap, -} - macro_rules! w { ($dst:expr, $($arg:tt)*) => { { let _ = write!($dst, $($arg)*); } @@ -49,6 +35,57 @@ macro_rules! wln { }; } +impl MirBody { + pub fn pretty_print(&self, db: &dyn HirDatabase) -> String { + let hir_body = db.body(self.owner); + let mut ctx = MirPrettyCtx::new(self, &hir_body, db); + ctx.for_body(|this| match ctx.body.owner { + hir_def::DefWithBodyId::FunctionId(id) => { + let data = db.function_data(id); + w!(this, "fn {}() ", data.name.display(db.upcast())); + } + hir_def::DefWithBodyId::StaticId(id) => { + let data = db.static_data(id); + w!(this, "static {}: _ = ", data.name.display(db.upcast())); + } + hir_def::DefWithBodyId::ConstId(id) => { + let data = db.const_data(id); + w!( + this, + "const {}: _ = ", + data.name.as_ref().unwrap_or(&Name::missing()).display(db.upcast()) + ); + } + hir_def::DefWithBodyId::VariantId(id) => { + let data = db.enum_data(id.parent); + w!(this, "enum {} = ", data.name.display(db.upcast())); + } + }); + ctx.result + } + + // String with lines is rendered poorly in `dbg` macros, which I use very much, so this + // function exists to solve that. + pub fn dbg(&self, db: &dyn HirDatabase) -> impl Debug { + struct StringDbg(String); + impl Debug for StringDbg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } + } + StringDbg(self.pretty_print(db)) + } +} + +struct MirPrettyCtx<'a> { + body: &'a MirBody, + hir_body: &'a Body, + db: &'a dyn HirDatabase, + result: String, + indent: String, + local_to_binding: ArenaMap, +} + impl Write for MirPrettyCtx<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { let mut it = s.split('\n'); // note: `.lines()` is wrong here @@ -66,31 +103,62 @@ enum LocalName { Binding(Name, LocalId), } -impl Display for LocalName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl HirDisplay for LocalName { + fn hir_fmt( + &self, + f: &mut crate::display::HirFormatter<'_>, + ) -> Result<(), crate::display::HirDisplayError> { match self { LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())), - LocalName::Binding(n, l) => write!(f, "{n}_{}", u32::from(l.into_raw())), + LocalName::Binding(n, l) => { + write!(f, "{}_{}", n.display(f.db.upcast()), u32::from(l.into_raw())) + } } } } impl<'a> MirPrettyCtx<'a> { - fn for_body(&mut self) { + fn for_body(&mut self, name: impl FnOnce(&mut MirPrettyCtx<'_>)) { + name(self); self.with_block(|this| { this.locals(); wln!(this); this.blocks(); }); + for &closure in &self.body.closures { + self.for_closure(closure); + } + } + + fn for_closure(&mut self, closure: ClosureId) { + let body = match self.db.mir_body_for_closure(closure) { + Ok(x) => x, + Err(e) => { + wln!(self, "// error in {closure:?}: {e:?}"); + return; + } + }; + let result = mem::take(&mut self.result); + let indent = mem::take(&mut self.indent); + let mut ctx = MirPrettyCtx { + body: &body, + local_to_binding: body.binding_locals.iter().map(|(x, y)| (*y, x)).collect(), + result, + indent, + ..*self + }; + ctx.for_body(|this| wln!(this, "// Closure: {:?}", closure)); + self.result = ctx.result; + self.indent = ctx.indent; } fn with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_>)) { - self.ident += " "; + self.indent += " "; wln!(self, "{{"); f(self); for _ in 0..4 { self.result.pop(); - self.ident.pop(); + self.indent.pop(); } wln!(self, "}}"); } @@ -101,7 +169,7 @@ fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self { body, db, result: String::new(), - ident: String::new(), + indent: String::new(), local_to_binding, hir_body, } @@ -109,7 +177,7 @@ fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self { fn write_line(&mut self) { self.result.push('\n'); - self.result += &self.ident; + self.result += &self.indent; } fn write(&mut self, line: &str) { @@ -118,7 +186,12 @@ fn write(&mut self, line: &str) { fn locals(&mut self) { for (id, local) in self.body.locals.iter() { - wln!(self, "let {}: {};", self.local_name(id), local.ty.display(self.db)); + wln!( + self, + "let {}: {};", + self.local_name(id).display(self.db), + self.hir_display(&local.ty) + ); } } @@ -147,10 +220,10 @@ fn blocks(&mut self) { wln!(this, ";"); } StatementKind::StorageDead(p) => { - wln!(this, "StorageDead({})", this.local_name(*p)); + wln!(this, "StorageDead({})", this.local_name(*p).display(self.db)); } StatementKind::StorageLive(p) => { - wln!(this, "StorageLive({})", this.local_name(*p)); + wln!(this, "StorageLive({})", this.local_name(*p).display(self.db)); } StatementKind::Deinit(p) => { w!(this, "Deinit("); @@ -161,11 +234,11 @@ fn blocks(&mut self) { } } match &block.terminator { - Some(terminator) => match terminator { - Terminator::Goto { target } => { + Some(terminator) => match &terminator.kind { + TerminatorKind::Goto { target } => { wln!(this, "goto 'bb{};", u32::from(target.into_raw())) } - Terminator::SwitchInt { discr, targets } => { + TerminatorKind::SwitchInt { discr, targets } => { w!(this, "switch "); this.operand(discr); w!(this, " "); @@ -176,7 +249,7 @@ fn blocks(&mut self) { wln!(this, "_ => {},", this.basic_block_id(targets.otherwise())); }); } - Terminator::Call { func, args, destination, target, .. } => { + TerminatorKind::Call { func, args, destination, target, .. } => { w!(this, "Call "); this.with_block(|this| { w!(this, "func: "); @@ -208,7 +281,7 @@ fn place(&mut self, p: &Place) { fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) { let Some((last, head)) = projections.split_last() else { // no projection - w!(this, "{}", this.local_name(local)); + w!(this, "{}", this.local_name(local).display(this.db)); return; }; match last { @@ -226,21 +299,26 @@ fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) { f(this, local, head); let variant_name = &this.db.enum_data(e.parent).variants[e.local_id].name; - w!(this, " as {}).{}", variant_name, name); + w!( + this, + " as {}).{}", + variant_name.display(this.db.upcast()), + name.display(this.db.upcast()) + ); } hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => { f(this, local, head); - w!(this, ".{name}"); + w!(this, ".{}", name.display(this.db.upcast())); } } } - ProjectionElem::TupleField(x) => { + ProjectionElem::TupleOrClosureField(x) => { f(this, local, head); w!(this, ".{}", x); } ProjectionElem::Index(l) => { f(this, local, head); - w!(this, "[{}]", this.local_name(*l)); + w!(this, "[{}]", this.local_name(*l).display(this.db)); } x => { f(this, local, head); @@ -258,7 +336,8 @@ fn operand(&mut self, r: &Operand) { // equally. Feel free to change it. self.place(p); } - Operand::Constant(c) => w!(self, "Const({})", c.display(self.db)), + Operand::Constant(c) => w!(self, "Const({})", self.hir_display(c)), + Operand::Static(s) => w!(self, "Static({:?})", s), } } @@ -284,11 +363,21 @@ fn rvalue(&mut self, r: &Rvalue) { self.operand_list(x); w!(self, "]"); } + Rvalue::Repeat(op, len) => { + w!(self, "["); + self.operand(op); + w!(self, "; {}]", len.display(self.db)); + } Rvalue::Aggregate(AggregateKind::Adt(_, _), x) => { w!(self, "Adt("); self.operand_list(x); w!(self, ")"); } + Rvalue::Aggregate(AggregateKind::Closure(_), x) => { + w!(self, "Closure("); + self.operand_list(x); + w!(self, ")"); + } Rvalue::Aggregate(AggregateKind::Union(_, _), x) => { w!(self, "Union("); self.operand_list(x); @@ -300,9 +389,9 @@ fn rvalue(&mut self, r: &Rvalue) { w!(self, ")"); } Rvalue::Cast(ck, op, ty) => { - w!(self, "Discriminant({ck:?}"); + w!(self, "Cast({ck:?}, "); self.operand(op); - w!(self, "{})", ty.display(self.db)); + w!(self, ", {})", self.hir_display(ty)); } Rvalue::CheckedBinaryOp(b, o1, o2) => { self.operand(o1); @@ -322,6 +411,7 @@ fn rvalue(&mut self, r: &Rvalue) { self.place(p); w!(self, ")"); } + Rvalue::ShallowInitBoxWithAlloc(_) => w!(self, "ShallowInitBoxWithAlloc"), Rvalue::ShallowInitBox(op, _) => { w!(self, "ShallowInitBox("); self.operand(op); @@ -345,4 +435,8 @@ fn operand_list(&mut self, x: &[Operand]) { } } } + + fn hir_display(&self, ty: &'a T) -> impl Display + 'a { + ty.display(self.db).with_closure_style(ClosureStyle::ClosureWithSubst) + } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs index 8c48331b94b5..7d19e0a19169 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs @@ -1,18 +1,18 @@ //! Database used for testing `hir`. -use std::{ - fmt, panic, - sync::{Arc, Mutex}, -}; +use std::{fmt, panic, sync::Mutex}; use base_db::{ - salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, + salsa::{self, Durability}, + AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, }; use hir_def::{db::DefDatabase, ModuleId}; use hir_expand::db::ExpandDatabase; -use stdx::hash::{NoHashHashMap, NoHashHashSet}; +use nohash_hasher::IntMap; +use rustc_hash::FxHashSet; use syntax::TextRange; use test_utils::extract_annotations; +use triomphe::Arc; #[salsa::database( base_db::SourceDatabaseExtStorage, @@ -30,7 +30,7 @@ pub(crate) struct TestDB { impl Default for TestDB { fn default() -> Self { let mut this = Self { storage: Default::default(), events: Default::default() }; - this.set_enable_proc_attr_macros(true); + this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH); this } } @@ -74,13 +74,13 @@ fn snapshot(&self) -> salsa::Snapshot { impl panic::RefUnwindSafe for TestDB {} impl FileLoader for TestDB { - fn file_text(&self, file_id: FileId) -> Arc { + fn file_text(&self, file_id: FileId) -> Arc { FileLoaderDelegate(self).file_text(file_id) } fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { FileLoaderDelegate(self).resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } } @@ -102,7 +102,7 @@ pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId { self.module_for_file_opt(file_id).unwrap() } - pub(crate) fn extract_annotations(&self) -> NoHashHashMap> { + pub(crate) fn extract_annotations(&self) -> IntMap> { let mut files = Vec::new(); let crate_graph = self.crate_graph(); for krate in crate_graph.iter() { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs index 83d31f002a1d..2db04024b7b6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs @@ -10,14 +10,14 @@ mod incremental; mod diagnostics; -use std::{collections::HashMap, env, sync::Arc}; +use std::{collections::HashMap, env}; use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt}; use expect_test::Expect; use hir_def::{ body::{Body, BodySourceMap, SyntheticSyntax}, db::{DefDatabase, InternDatabase}, - expr::{ExprId, PatId}, + hir::{ExprId, Pat, PatId}, item_scope::ItemScope, nameres::DefMap, src::HasSource, @@ -32,6 +32,7 @@ }; use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; use tracing_tree::HierarchicalLayer; +use triomphe::Arc; use crate::{ db::HirDatabase, @@ -148,10 +149,13 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour }); let mut unexpected_type_mismatches = String::new(); for def in defs { - let (_body, body_source_map) = db.body_with_source_map(def); + let (body, body_source_map) = db.body_with_source_map(def); let inference_result = db.infer(def); - for (pat, ty) in inference_result.type_of_pat.iter() { + for (pat, mut ty) in inference_result.type_of_pat.iter() { + if let Pat::Bind { id, .. } = body.pats[pat] { + ty = &inference_result.type_of_binding[id]; + } let node = match pat_node(&body_source_map, pat, &db) { Some(value) => value, None => continue, @@ -159,7 +163,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour let range = node.as_ref().original_file_range(&db); if let Some(expected) = types.remove(&range) { let actual = if display_source { - ty.display_source_code(&db, def.module(&db)).unwrap() + ty.display_source_code(&db, def.module(&db), true).unwrap() } else { ty.display_test(&db).to_string() }; @@ -175,7 +179,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour let range = node.as_ref().original_file_range(&db); if let Some(expected) = types.remove(&range) { let actual = if display_source { - ty.display_source_code(&db, def.module(&db)).unwrap() + ty.display_source_code(&db, def.module(&db), true).unwrap() } else { ty.display_test(&db).to_string() }; @@ -198,8 +202,8 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour for (expr_or_pat, mismatch) in inference_result.type_mismatches() { let Some(node) = (match expr_or_pat { - hir_def::expr::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db), - hir_def::expr::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db), + hir_def::hir::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db), + hir_def::hir::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db), }) else { continue; }; let range = node.as_ref().original_file_range(&db); let actual = format!( @@ -246,7 +250,7 @@ fn expr_node( ) -> Option> { Some(match body_source_map.expr_syntax(expr) { Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); + let root = db.parse_or_expand(sp.file_id); sp.map(|ptr| ptr.to_node(&root).syntax().clone()) } Err(SyntheticSyntax) => return None, @@ -260,7 +264,7 @@ fn pat_node( ) -> Option> { Some(match body_source_map.pat_syntax(pat) { Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); + let root = db.parse_or_expand(sp.file_id); sp.map(|ptr| { ptr.either( |it| it.to_node(&root).syntax().clone(), @@ -283,14 +287,18 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { let mut buf = String::new(); let mut infer_def = |inference_result: Arc, + body: Arc, body_source_map: Arc| { let mut types: Vec<(InFile, &Ty)> = Vec::new(); let mut mismatches: Vec<(InFile, &TypeMismatch)> = Vec::new(); - for (pat, ty) in inference_result.type_of_pat.iter() { + for (pat, mut ty) in inference_result.type_of_pat.iter() { + if let Pat::Bind { id, .. } = body.pats[pat] { + ty = &inference_result.type_of_binding[id]; + } let syntax_ptr = match body_source_map.pat_syntax(pat) { Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); + let root = db.parse_or_expand(sp.file_id); sp.map(|ptr| { ptr.either( |it| it.to_node(&root).syntax().clone(), @@ -309,7 +317,7 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { for (expr, ty) in inference_result.type_of_expr.iter() { let node = match body_source_map.expr_syntax(expr) { Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); + let root = db.parse_or_expand(sp.file_id); sp.map(|ptr| ptr.to_node(&root).syntax().clone()) } Err(SyntheticSyntax) => continue, @@ -385,9 +393,9 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { } }); for def in defs { - let (_body, source_map) = db.body_with_source_map(def); + let (body, source_map) = db.body_with_source_map(def); let infer = db.infer(def); - infer_def(infer, source_map); + infer_def(infer, body, source_map); } buf.truncate(buf.trim_end().len()); @@ -572,10 +580,9 @@ fn main() { let x = 1; x.push(1); } - " - .to_string(); + "; - db.set_file_text(pos.file_id, Arc::new(new_text)); + db.set_file_text(pos.file_id, Arc::from(new_text)); let module = db.module_for_file(pos.file_id); let crate_def_map = module.def_map(&db); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs index b524922b6cf4..16e5ef85d09d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs @@ -258,7 +258,6 @@ fn test() { #[test] fn coerce_autoderef_block() { - // FIXME: We should know mutability in overloaded deref check_no_mismatches( r#" //- minicore: deref @@ -268,7 +267,7 @@ fn takes_ref_str(x: &str) {} fn returns_string() -> String { loop {} } fn test() { takes_ref_str(&{ returns_string() }); - // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(None))), Borrow(Ref(Not)) + // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref(Not)) } "#, ); @@ -396,10 +395,40 @@ fn test() { ); } +#[test] +fn coerce_fn_item_to_fn_ptr_in_array() { + check_no_mismatches( + r" +fn foo(x: u32) -> isize { 1 } +fn bar(x: u32) -> isize { 1 } +fn test() { + let f = [foo, bar]; + // ^^^ adjustments: Pointer(ReifyFnPointer) +}", + ); +} + #[test] fn coerce_fn_items_in_match_arms() { cov_mark::check!(coerce_fn_reification); + check_no_mismatches( + r" +fn foo1(x: u32) -> isize { 1 } +fn foo2(x: u32) -> isize { 2 } +fn foo3(x: u32) -> isize { 3 } +fn test() { + let x = match 1 { + 1 => foo1, + // ^^^^ adjustments: Pointer(ReifyFnPointer) + 2 => foo2, + // ^^^^ adjustments: Pointer(ReifyFnPointer) + _ => foo3, + // ^^^^ adjustments: Pointer(ReifyFnPointer) + }; + x; +}", + ); check_types( r" fn foo1(x: u32) -> isize { 1 } @@ -507,7 +536,6 @@ fn test() { #[test] fn coerce_unsize_generic() { - // FIXME: fix the type mismatches here check( r#" //- minicore: coerce_unsized @@ -516,9 +544,9 @@ fn coerce_unsize_generic() { fn test() { let _: &Foo<[usize]> = &Foo { t: [1, 2, 3] }; - //^^^^^^^^^ expected [usize], got [usize; 3] + //^^^^^^^^^^^^^^^^^^^^^ expected &Foo<[usize]>, got &Foo<[i32; 3]> let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] }); - //^^^^^^^^^ expected [usize], got [usize; 3] + //^^^^^^^^^^^^^^^^^^^^^^^^^^ expected &Bar<[usize]>, got &Bar<[i32; 3]> } "#, ); @@ -547,7 +575,7 @@ fn two_closures_lub() { fn foo(c: i32) { let add = |a: i32, b: i32| a + b; let sub = |a, b| a - b; - //^^^^^^^^^^^^ |i32, i32| -> i32 + //^^^^^^^^^^^^ impl Fn(i32, i32) -> i32 if c > 42 { add } else { sub }; //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fn(i32, i32) -> i32 } @@ -842,3 +870,74 @@ fn test() { }", ); } + +#[test] +fn adjust_index() { + check_no_mismatches( + r" +//- minicore: index, slice, coerce_unsized +fn test() { + let x = [1, 2, 3]; + x[2] = 6; + // ^ adjustments: Borrow(Ref(Mut)) +} + ", + ); + check_no_mismatches( + r" +//- minicore: index +struct Struct; +impl core::ops::Index for Struct { + type Output = (); + + fn index(&self, index: usize) -> &Self::Output { &() } +} +struct StructMut; + +impl core::ops::Index for StructMut { + type Output = (); + + fn index(&self, index: usize) -> &Self::Output { &() } +} +impl core::ops::IndexMut for StructMut { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut () } +} +fn test() { + Struct[0]; + // ^^^^^^ adjustments: Borrow(Ref(Not)) + StructMut[0]; + // ^^^^^^^^^ adjustments: Borrow(Ref(Not)) + &mut StructMut[0]; + // ^^^^^^^^^ adjustments: Borrow(Ref(Mut)) +}", + ); +} + +#[test] +fn regression_14443_dyn_coercion_block_impls() { + check_no_mismatches( + r#" +//- minicore: coerce_unsized +trait T {} + +fn dyn_t(d: &dyn T) {} + +fn main() { + struct A; + impl T for A {} + + let a = A; + + let b = { + struct B; + impl T for B {} + + B + }; + + dyn_t(&a); + dyn_t(&b); +} +"#, + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs index 073d6d9be2b9..bb15ca8c436a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs @@ -1,6 +1,5 @@ -use std::sync::Arc; - use base_db::{fixture::WithFixture, SourceDatabaseExt}; +use triomphe::Arc; use crate::{db::HirDatabase, test_db::TestDB}; @@ -33,10 +32,9 @@ fn foo() -> i32 { + 1 } - " - .to_string(); + "; - db.set_file_text(pos.file_id, Arc::new(new_text)); + db.set_file_text(pos.file_id, Arc::from(new_text)); { let events = db.log_executed(|| { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs index 8b75ec842a4f..111ac0b618eb 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs @@ -140,6 +140,7 @@ fn main() { fn expr_macro_def_expanded_in_various_places() { check_infer( r#" + //- minicore: iterator macro spam() { 1isize } @@ -195,10 +196,19 @@ fn spam() { !0..6 '1isize': isize 39..442 '{ ...!(); }': () 73..94 'spam!(...am!())': {unknown} + 100..119 'for _ ...!() {}': fn into_iter(isize) -> ::IntoIter + 100..119 'for _ ...!() {}': IntoIterator::IntoIter + 100..119 'for _ ...!() {}': ! + 100..119 'for _ ...!() {}': IntoIterator::IntoIter + 100..119 'for _ ...!() {}': &mut IntoIterator::IntoIter + 100..119 'for _ ...!() {}': fn next>(&mut IntoIterator::IntoIter) -> Option< as Iterator>::Item> + 100..119 'for _ ...!() {}': Option>> 100..119 'for _ ...!() {}': () - 104..105 '_': {unknown} + 100..119 'for _ ...!() {}': () + 100..119 'for _ ...!() {}': () + 104..105 '_': Iterator::Item> 117..119 '{}': () - 124..134 '|| spam!()': || -> isize + 124..134 '|| spam!()': impl Fn() -> isize 140..156 'while ...!() {}': () 154..156 '{}': () 161..174 'break spam!()': ! @@ -221,6 +231,7 @@ fn spam() { fn expr_macro_rules_expanded_in_various_places() { check_infer( r#" + //- minicore: iterator macro_rules! spam { () => (1isize); } @@ -276,10 +287,19 @@ fn spam() { !0..6 '1isize': isize 53..456 '{ ...!(); }': () 87..108 'spam!(...am!())': {unknown} + 114..133 'for _ ...!() {}': fn into_iter(isize) -> ::IntoIter + 114..133 'for _ ...!() {}': IntoIterator::IntoIter + 114..133 'for _ ...!() {}': ! + 114..133 'for _ ...!() {}': IntoIterator::IntoIter + 114..133 'for _ ...!() {}': &mut IntoIterator::IntoIter + 114..133 'for _ ...!() {}': fn next>(&mut IntoIterator::IntoIter) -> Option< as Iterator>::Item> + 114..133 'for _ ...!() {}': Option>> 114..133 'for _ ...!() {}': () - 118..119 '_': {unknown} + 114..133 'for _ ...!() {}': () + 114..133 'for _ ...!() {}': () + 118..119 '_': Iterator::Item> 131..133 '{}': () - 138..148 '|| spam!()': || -> isize + 138..148 '|| spam!()': impl Fn() -> isize 154..170 'while ...!() {}': () 168..170 '{}': () 175..188 'break spam!()': ! @@ -661,8 +681,9 @@ fn main() { "#, expect![[r#" !0..1 '0': i32 + !0..6 '0asu32': u32 63..87 '{ ...!(); }': () - 73..74 'x': i32 + 73..74 'x': u32 "#]], ); } @@ -699,8 +720,9 @@ fn main() { "#, expect![[r#" !0..1 '0': i32 + !0..6 '0asu32': u32 65..91 '{ ...!(); }': () - 75..76 'x': i32 + 75..76 'x': u32 "#]], ); } @@ -945,7 +967,7 @@ fn main() { #[test] fn infer_builtin_macros_env() { - check_infer( + check_types( r#" //- /main.rs env:foo=bar #[rustc_builtin_macro] @@ -953,13 +975,26 @@ macro_rules! env {() => {}} fn main() { let x = env!("foo"); + //^ &str + } + "#, + ); +} + +#[test] +fn infer_builtin_macros_option_env() { + check_types( + r#" + //- minicore: option + //- /main.rs env:foo=bar + #[rustc_builtin_macro] + macro_rules! option_env {() => {}} + + fn main() { + let x = option_env!("foo"); + //^ Option<&str> } "#, - expect![[r#" - !0..22 '"__RA_...TED__"': &str - 62..90 '{ ...o"); }': () - 72..73 'x': &str - "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs index 378d47833610..1e57a4ae2968 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs @@ -388,6 +388,24 @@ fn test() { ); } +#[test] +fn infer_trait_method_multiple_mutable_reference() { + check_types( + r#" +trait Trait { + fn method(&mut self) -> i32 { 5 } +} +struct S; +impl Trait for &mut &mut S {} +fn test() { + let s = &mut &mut &mut S; + s.method(); + //^^^^^^^^^^ i32 +} + "#, + ); +} + #[test] fn infer_trait_method_generic_1() { // the trait implementation is intentionally incomplete -- it shouldn't matter @@ -1255,7 +1273,6 @@ fn foo(a: &T) { #[test] fn autoderef_visibility_field() { - // FIXME: We should know mutability in overloaded deref check( r#" //- minicore: deref @@ -1277,7 +1294,7 @@ fn deref(&self) -> &Foo { mod b { fn foo() { let x = super::a::Bar::new().0; - // ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(None))) + // ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Some(Not)))) // ^^^^^^^^^^^^^^^^^^^^^^ type: char } } @@ -1723,7 +1740,7 @@ fn test() { Foo.foo(); //^^^ adjustments: Borrow(Ref(Not)) (&Foo).foo(); - // ^^^^ adjustments: , + // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)) } "#, ); @@ -1922,3 +1939,54 @@ fn foo() { "#, ); } + +#[test] +fn box_deref_is_builtin() { + check( + r#" +//- minicore: deref +use core::ops::Deref; + +#[lang = "owned_box"] +struct Box(*mut T); + +impl Box { + fn new(t: T) -> Self { + loop {} + } +} + +impl Deref for Box { + type Target = T; + fn deref(&self) -> &Self::Target; +} + +struct Foo; +impl Foo { + fn foo(&self) {} +} +fn test() { + Box::new(Foo).foo(); + //^^^^^^^^^^^^^ adjustments: Deref(None), Borrow(Ref(Not)) +} +"#, + ); +} + +#[test] +fn manually_drop_deref_is_not_builtin() { + check( + r#" +//- minicore: manually_drop, deref +struct Foo; +impl Foo { + fn foo(&self) {} +} +use core::mem::ManuallyDrop; +fn test() { + ManuallyDrop::new(Foo).foo(); + //^^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref(Not)) +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs index fbdc8209f8f4..59046c0435a3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs @@ -327,6 +327,7 @@ fn test1() { fn diverging_expression_3_break() { check_infer_with_mismatches( r" + //- minicore: iterator //- /main.rs fn test1() { // should give type mismatch @@ -360,6 +361,15 @@ fn test3() { 97..343 '{ ...; }; }': () 140..141 'x': u32 149..175 '{ for ...; }; }': u32 + 151..172 'for a ...eak; }': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter + 151..172 'for a ...eak; }': {unknown} + 151..172 'for a ...eak; }': ! + 151..172 'for a ...eak; }': {unknown} + 151..172 'for a ...eak; }': &mut {unknown} + 151..172 'for a ...eak; }': fn next<{unknown}>(&mut {unknown}) -> Option<<{unknown} as Iterator>::Item> + 151..172 'for a ...eak; }': Option<{unknown}> + 151..172 'for a ...eak; }': () + 151..172 'for a ...eak; }': () 151..172 'for a ...eak; }': () 155..156 'a': {unknown} 160..161 'b': {unknown} @@ -367,12 +377,30 @@ fn test3() { 164..169 'break': ! 226..227 'x': u32 235..253 '{ for ... {}; }': u32 + 237..250 'for a in b {}': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter + 237..250 'for a in b {}': {unknown} + 237..250 'for a in b {}': ! + 237..250 'for a in b {}': {unknown} + 237..250 'for a in b {}': &mut {unknown} + 237..250 'for a in b {}': fn next<{unknown}>(&mut {unknown}) -> Option<<{unknown} as Iterator>::Item> + 237..250 'for a in b {}': Option<{unknown}> + 237..250 'for a in b {}': () + 237..250 'for a in b {}': () 237..250 'for a in b {}': () 241..242 'a': {unknown} 246..247 'b': {unknown} 248..250 '{}': () 304..305 'x': u32 313..340 '{ for ...; }; }': u32 + 315..337 'for a ...urn; }': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter + 315..337 'for a ...urn; }': {unknown} + 315..337 'for a ...urn; }': ! + 315..337 'for a ...urn; }': {unknown} + 315..337 'for a ...urn; }': &mut {unknown} + 315..337 'for a ...urn; }': fn next<{unknown}>(&mut {unknown}) -> Option<<{unknown} as Iterator>::Item> + 315..337 'for a ...urn; }': Option<{unknown}> + 315..337 'for a ...urn; }': () + 315..337 'for a ...urn; }': () 315..337 'for a ...urn; }': () 319..320 'a': {unknown} 324..325 'b': {unknown} @@ -483,3 +511,22 @@ fn example() -> bool { "#, ); } + +#[test] +fn reservation_impl_should_be_ignored() { + // See rust-lang/rust#64631. + check_types( + r#" +//- minicore: from +struct S; +#[rustc_reservation_impl] +impl From for T {} +fn foo>(_: U) -> T { loop {} } + +fn test() { + let s = foo(S); + //^ S +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs index 74bcab6caa94..0f5a3e1752cf 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs @@ -1,11 +1,12 @@ use expect_test::expect; -use super::{check, check_infer, check_infer_with_mismatches, check_types}; +use super::{check, check_infer, check_infer_with_mismatches, check_no_mismatches, check_types}; #[test] fn infer_pattern() { check_infer( r#" + //- minicore: iterator fn test(x: &i32) { let y = x; let &z = x; @@ -46,6 +47,15 @@ fn test(x: &i32) { 82..94 '(1, "hello")': (i32, &str) 83..84 '1': i32 86..93 '"hello"': &str + 101..151 'for (e... }': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter + 101..151 'for (e... }': {unknown} + 101..151 'for (e... }': ! + 101..151 'for (e... }': {unknown} + 101..151 'for (e... }': &mut {unknown} + 101..151 'for (e... }': fn next<{unknown}>(&mut {unknown}) -> Option<<{unknown} as Iterator>::Item> + 101..151 'for (e... }': Option<({unknown}, {unknown})> + 101..151 'for (e... }': () + 101..151 'for (e... }': () 101..151 'for (e... }': () 105..111 '(e, f)': ({unknown}, {unknown}) 106..107 'e': {unknown} @@ -70,8 +80,8 @@ fn test(x: &i32) { 228..233 '&true': &bool 229..233 'true': bool 234..236 '{}': () - 246..252 'lambda': |u64, u64, i32| -> i32 - 255..287 '|a: u6...b; c }': |u64, u64, i32| -> i32 + 246..252 'lambda': impl Fn(u64, u64, i32) -> i32 + 255..287 '|a: u6...b; c }': impl Fn(u64, u64, i32) -> i32 256..257 'a': u64 264..265 'b': u64 267..268 'c': i32 @@ -240,6 +250,21 @@ fn test() { ); } +#[test] +fn ref_pat_with_inference_variable() { + check_no_mismatches( + r#" +enum E { A } +fn test() { + let f = |e| match e { + &E::A => {} + }; + f(&E::A); +} +"#, + ); +} + #[test] fn infer_pattern_match_slice() { check_infer( @@ -476,7 +501,7 @@ fn test() { 183..184 'x': usize 190..191 'x': usize 201..205 'E::B': E - 209..212 'foo': {unknown} + 209..212 'foo': bool 216..217 '1': usize 227..231 'E::B': E 235..237 '10': usize @@ -677,25 +702,25 @@ fn test() { 51..58 'loop {}': ! 56..58 '{}': () 72..171 '{ ... x); }': () - 78..81 'foo': fn foo<&(i32, &str), i32, |&(i32, &str)| -> i32>(&(i32, &str), |&(i32, &str)| -> i32) -> i32 + 78..81 'foo': fn foo<&(i32, &str), i32, impl Fn(&(i32, &str)) -> i32>(&(i32, &str), impl Fn(&(i32, &str)) -> i32) -> i32 78..105 'foo(&(...y)| x)': i32 82..91 '&(1, "a")': &(i32, &str) 83..91 '(1, "a")': (i32, &str) 84..85 '1': i32 87..90 '"a"': &str - 93..104 '|&(x, y)| x': |&(i32, &str)| -> i32 + 93..104 '|&(x, y)| x': impl Fn(&(i32, &str)) -> i32 94..101 '&(x, y)': &(i32, &str) 95..101 '(x, y)': (i32, &str) 96..97 'x': i32 99..100 'y': &str 103..104 'x': i32 - 142..145 'foo': fn foo<&(i32, &str), &i32, |&(i32, &str)| -> &i32>(&(i32, &str), |&(i32, &str)| -> &i32) -> &i32 + 142..145 'foo': fn foo<&(i32, &str), &i32, impl Fn(&(i32, &str)) -> &i32>(&(i32, &str), impl Fn(&(i32, &str)) -> &i32) -> &i32 142..168 'foo(&(...y)| x)': &i32 146..155 '&(1, "a")': &(i32, &str) 147..155 '(1, "a")': (i32, &str) 148..149 '1': i32 151..154 '"a"': &str - 157..167 '|(x, y)| x': |&(i32, &str)| -> &i32 + 157..167 '|(x, y)| x': impl Fn(&(i32, &str)) -> &i32 158..164 '(x, y)': (i32, &str) 159..160 'x': &i32 162..163 'y': &&str @@ -1084,7 +1109,7 @@ fn var_args() { #[lang = "va_list"] pub struct VaListImpl<'f>; fn my_fn(foo: ...) {} - //^^^ VaListImpl + //^^^ VaListImpl<'_> "#, ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index 689f0da44f68..f18c953a7afd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -246,6 +246,7 @@ fn infer_std_crash_5() { // taken from rustc check_infer( r#" + //- minicore: iterator fn extra_compiler_flags() { for content in doesnt_matter { let name = if doesnt_matter { @@ -264,13 +265,22 @@ fn extra_compiler_flags() { "#, expect![[r#" 26..322 '{ ... } }': () + 32..320 'for co... }': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter + 32..320 'for co... }': {unknown} + 32..320 'for co... }': ! + 32..320 'for co... }': {unknown} + 32..320 'for co... }': &mut {unknown} + 32..320 'for co... }': fn next<{unknown}>(&mut {unknown}) -> Option<<{unknown} as Iterator>::Item> + 32..320 'for co... }': Option<{unknown}> + 32..320 'for co... }': () + 32..320 'for co... }': () 32..320 'for co... }': () 36..43 'content': {unknown} 47..60 'doesnt_matter': {unknown} 61..320 '{ ... }': () 75..79 'name': &{unknown} 82..166 'if doe... }': &{unknown} - 85..98 'doesnt_matter': {unknown} + 85..98 'doesnt_matter': bool 99..128 '{ ... }': &{unknown} 113..118 'first': &{unknown} 134..166 '{ ... }': &{unknown} @@ -279,7 +289,7 @@ fn extra_compiler_flags() { 181..188 'content': &{unknown} 191..313 'if ICE... }': &{unknown} 194..231 'ICE_RE..._VALUE': {unknown} - 194..247 'ICE_RE...&name)': {unknown} + 194..247 'ICE_RE...&name)': bool 241..246 '&name': &&{unknown} 242..246 'name': &{unknown} 248..276 '{ ... }': &{unknown} @@ -805,19 +815,19 @@ fn main() { 225..229 'iter': T 244..246 '{}': Vec 258..402 '{ ...r(); }': () - 268..273 'inner': Map<|&f64| -> f64> - 276..300 'Map { ... 0.0 }': Map<|&f64| -> f64> - 285..298 '|_: &f64| 0.0': |&f64| -> f64 + 268..273 'inner': Map f64> + 276..300 'Map { ... 0.0 }': Map f64> + 285..298 '|_: &f64| 0.0': impl Fn(&f64) -> f64 286..287 '_': &f64 295..298 '0.0': f64 - 311..317 'repeat': Repeat f64>> - 320..345 'Repeat...nner }': Repeat f64>> - 338..343 'inner': Map<|&f64| -> f64> - 356..359 'vec': Vec f64>>>> - 362..371 'from_iter': fn from_iter f64>>>, Repeat f64>>>(Repeat f64>>) -> Vec f64>>>> - 362..379 'from_i...epeat)': Vec f64>>>> - 372..378 'repeat': Repeat f64>> - 386..389 'vec': Vec f64>>>> + 311..317 'repeat': Repeat f64>> + 320..345 'Repeat...nner }': Repeat f64>> + 338..343 'inner': Map f64> + 356..359 'vec': Vec f64>>>> + 362..371 'from_iter': fn from_iter f64>>>, Repeat f64>>>(Repeat f64>>) -> Vec f64>>>> + 362..379 'from_i...epeat)': Vec f64>>>> + 372..378 'repeat': Repeat f64>> + 386..389 'vec': Vec f64>>>> 386..399 'vec.foo_bar()': {unknown} "#]], ); @@ -852,7 +862,7 @@ fn main() { 123..126 'S()': S 132..133 's': S 132..144 's.g(|_x| {})': () - 136..143 '|_x| {}': |&i32| -> () + 136..143 '|_x| {}': impl Fn(&i32) 137..139 '_x': &i32 141..143 '{}': () 150..151 's': S @@ -886,13 +896,13 @@ fn flush(&self) { "#, expect![[r#" 123..127 'self': &Mutex - 150..152 '{}': MutexGuard + 150..152 '{}': MutexGuard<'_, T> 234..238 'self': &{unknown} 240..290 '{ ...()); }': () 250..251 'w': &Mutex 276..287 '*(w.lock())': BufWriter 278..279 'w': &Mutex - 278..286 'w.lock()': MutexGuard + 278..286 'w.lock()': MutexGuard<'_, BufWriter> "#]], ); } @@ -1060,13 +1070,30 @@ fn infix_parse(_state: S, _level_code: &Fn(S)) -> T { loop {} } -fn parse_arule() { +fn parse_a_rule() { infix_parse((), &(|_recurse| ())) } "#, ) } +#[test] +fn nested_closure() { + check_types( + r#" +//- minicore: fn, option + +fn map(o: Option, f: impl FnOnce(T) -> U) -> Option { loop {} } + +fn test() { + let o = Some(Some(2)); + map(o, |s| map(s, |x| x)); + // ^ i32 +} + "#, + ); +} + #[test] fn call_expected_type_closure() { check_types( @@ -1198,6 +1225,7 @@ fn mamba(a: U32!(), p: u32) -> u32 { fn for_loop_block_expr_iterable() { check_infer( r#" +//- minicore: iterator fn test() { for _ in { let x = 0; } { let y = 0; @@ -1206,8 +1234,17 @@ fn test() { "#, expect![[r#" 10..68 '{ ... } }': () + 16..66 'for _ ... }': fn into_iter<()>(()) -> <() as IntoIterator>::IntoIter + 16..66 'for _ ... }': IntoIterator::IntoIter<()> + 16..66 'for _ ... }': ! + 16..66 'for _ ... }': IntoIterator::IntoIter<()> + 16..66 'for _ ... }': &mut IntoIterator::IntoIter<()> + 16..66 'for _ ... }': fn next>(&mut IntoIterator::IntoIter<()>) -> Option< as Iterator>::Item> + 16..66 'for _ ... }': Option>> 16..66 'for _ ... }': () - 20..21 '_': {unknown} + 16..66 'for _ ... }': () + 16..66 'for _ ... }': () + 20..21 '_': Iterator::Item> 25..39 '{ let x = 0; }': () 31..32 'x': i32 35..36 '0': i32 @@ -1458,13 +1495,12 @@ fn regression_11688_3() { struct Ar(T); fn f( num_zeros: usize, - ) -> dyn Iterator; LEN]> { + ) -> &dyn Iterator; LEN]> { loop {} } fn dynamic_programming() { - for board in f::<9, u8, 7>(1) { - //^^^^^ [Ar; 9] - } + let board = f::<9, u8, 7>(1).next(); + //^^^^^ Option<[Ar; 9]> } "#, ); @@ -1757,6 +1793,21 @@ trait Tr {} ); } +#[test] +fn regression_14456() { + check_types( + r#" +//- minicore: future +async fn x() {} +fn f() { + let fut = x(); + let t = [0u8; { let a = 2 + 2; a }]; + //^ [u8; 4] +} +"#, + ); +} + #[test] fn regression_14164() { check_types( @@ -1788,3 +1839,119 @@ fn test() "#, ); } + +#[test] +fn match_ergonomics_with_binding_modes_interaction() { + check_types( + r" +enum E { A } +fn foo() { + match &E::A { + b @ (x @ E::A | x) => { + b; + //^ &E + x; + //^ &E + } + } +}", + ); +} + +#[test] +fn regression_14844() { + check_no_mismatches( + r#" +pub type Ty = Unknown; + +pub struct Inner(); + +pub struct Outer { + pub inner: Inner, +} + +fn main() { + _ = Outer { + inner: Inner::(), + }; +} + "#, + ); + check_no_mismatches( + r#" +pub const ONE: usize = 1; + +pub struct Inner(); + +pub struct Outer { + pub inner: Inner, +} + +fn main() { + _ = Outer { + inner: Inner::<1>(), + }; +} + "#, + ); + check_no_mismatches( + r#" +pub const ONE: usize = unknown(); + +pub struct Inner(); + +pub struct Outer { + pub inner: Inner, +} + +fn main() { + _ = Outer { + inner: Inner::<1>(), + }; +} + "#, + ); + check_no_mismatches( + r#" +pub const N: usize = 2 + 2; + +fn f(t: [u8; N]) {} + +fn main() { + let a = [1, 2, 3, 4]; + f(a); + let b = [1; 4]; + let c: [u8; N] = b; + let d = [1; N]; + let e: [u8; N] = d; + let f = [1; N]; + let g = match f { + [a, b, c, d] => a + b + c + d, + }; +} + "#, + ); +} + +#[test] +fn regression_14844_2() { + check_no_mismatches( + r#" +//- minicore: fn +pub const ONE: usize = 1; + +pub type MyInner = Inner; + +pub struct Inner(); + +impl Inner<1> { + fn map(&self, func: F) -> bool + where + F: Fn(&MyInner) -> bool, + { + func(self) + } +} + "#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 13cc3fea52d1..3ece40486dde 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -854,9 +854,9 @@ fn test2(a1: *const A, a2: *mut A) { 237..239 'a2': *mut A 249..272 '{ ...2.b; }': () 255..257 'a1': *const A - 255..259 'a1.b': B + 255..259 'a1.b': {unknown} 265..267 'a2': *mut A - 265..269 'a2.b': B + 265..269 'a2.b': {unknown} "#]], ); } @@ -1812,6 +1812,20 @@ fn main() { //^ [(); 7] }"#, ); + check_types( + r#" +trait Foo { + fn x(self); +} + +impl Foo for u8 { + fn x(self) { + let t = [0; 4 + 2]; + //^ [i32; 6] + } +} + "#, + ); } #[test] @@ -1906,8 +1920,8 @@ fn foo() -> u32 { "#, expect![[r#" 16..58 '{ ...; }; }': u32 - 26..27 'x': || -> usize - 30..55 '|| -> ...n 1; }': || -> usize + 26..27 'x': impl Fn() -> usize + 30..55 '|| -> ...n 1; }': impl Fn() -> usize 42..55 '{ return 1; }': usize 44..52 'return 1': ! 51..52 '1': usize @@ -1925,8 +1939,8 @@ fn foo() -> u32 { "#, expect![[r#" 16..47 '{ ...; }; }': u32 - 26..27 'x': || -> () - 30..44 '|| { return; }': || -> () + 26..27 'x': impl Fn() + 30..44 '|| { return; }': impl Fn() 33..44 '{ return; }': () 35..41 'return': ! "#]], @@ -1943,8 +1957,8 @@ fn foo() -> u32 { "#, expect![[r#" 16..46 '{ ..." }; }': u32 - 26..27 'x': || -> &str - 30..43 '|| { "test" }': || -> &str + 26..27 'x': impl Fn() -> &str + 30..43 '|| { "test" }': impl Fn() -> &str 33..43 '{ "test" }': &str 35..41 '"test"': &str "#]], @@ -2033,6 +2047,56 @@ fn test() { ); } +#[test] +fn tuple_pattern_nested_match_ergonomics() { + check_no_mismatches( + r#" +fn f(x: (&i32, &i32)) -> i32 { + match x { + (3, 4) => 5, + _ => 12, + } +} + "#, + ); + check_types( + r#" +fn f(x: (&&&&i32, &&&i32)) { + let f = match x { + t @ (3, 4) => t, + _ => loop {}, + }; + f; + //^ (&&&&i32, &&&i32) +} + "#, + ); + check_types( + r#" +fn f() { + let x = &&&(&&&2, &&&&&3); + let (y, z) = x; + //^ &&&&i32 + let t @ (y, z) = x; + t; + //^ &&&(&&&i32, &&&&&i32) +} + "#, + ); + check_types( + r#" +fn f() { + let x = &&&(&&&2, &&&&&3); + let (y, z) = x; + //^ &&&&i32 + let t @ (y, z) = x; + t; + //^ &&&(&&&i32, &&&&&i32) +} + "#, + ); +} + #[test] fn fn_pointer_return() { check_infer( @@ -2050,7 +2114,7 @@ fn main() { 47..120 '{ ...hod; }': () 57..63 'vtable': Vtable 66..90 'Vtable...| {} }': Vtable - 83..88 '|| {}': || -> () + 83..88 '|| {}': impl Fn() 86..88 '{}': () 100..101 'm': fn() 104..110 'vtable': Vtable @@ -2087,6 +2151,7 @@ async fn main() { 136..138 '()': () 150..151 'w': i32 154..166 'const { 92 }': i32 + 154..166 'const { 92 }': i32 162..164 '92': i32 176..177 't': i32 180..190 ''a: { 92 }': i32 @@ -2094,6 +2159,24 @@ async fn main() { "#]], ) } + +#[test] +fn async_fn_and_try_operator() { + check_no_mismatches( + r#" +//- minicore: future, result, fn, try, from +async fn foo() -> Result<(), ()> { + Ok(()) +} + +async fn bar() -> Result<(), ()> { + let x = foo().await?; + Ok(x) +} + "#, + ) +} + #[test] fn async_block_early_return() { check_infer( @@ -2124,9 +2207,9 @@ fn main() { 149..151 'Ok': Ok<(), ()>(()) -> Result<(), ()> 149..155 'Ok(())': Result<(), ()> 152..154 '()': () - 167..171 'test': fn test<(), (), || -> impl Future>, impl Future>>(|| -> impl Future>) + 167..171 'test': fn test<(), (), impl Fn() -> impl Future>, impl Future>>(impl Fn() -> impl Future>) 167..228 'test(|... })': () - 172..227 '|| asy... }': || -> impl Future> + 172..227 '|| asy... }': impl Fn() -> impl Future> 175..227 'async ... }': impl Future> 191..205 'return Err(())': ! 198..201 'Err': Err<(), ()>(()) -> Result<(), ()> @@ -2252,8 +2335,8 @@ fn foo() { "#, expect![[r#" 9..335 '{ ... }; }': () - 19..21 '_x': || -> bool - 24..332 '|| 'ou... }': || -> bool + 19..21 '_x': impl Fn() -> bool + 24..332 '|| 'ou... }': impl Fn() -> bool 27..332 ''outer... }': bool 40..332 '{ ... }': () 54..59 'inner': i8 @@ -2677,6 +2760,179 @@ impl B for Astruct {} ) } +#[test] +fn capture_kinds_simple() { + check_types( + r#" +struct S; + +impl S { + fn read(&self) -> &S { self } + fn write(&mut self) -> &mut S { self } + fn consume(self) -> S { self } +} + +fn f() { + let x = S; + let c1 = || x.read(); + //^^ impl Fn() -> &S + let c2 = || x.write(); + //^^ impl FnMut() -> &mut S + let c3 = || x.consume(); + //^^ impl FnOnce() -> S + let c3 = || x.consume().consume().consume(); + //^^ impl FnOnce() -> S + let c3 = || x.consume().write().read(); + //^^ impl FnOnce() -> &S + let x = &mut x; + let c1 = || x.write(); + //^^ impl FnMut() -> &mut S + let x = S; + let c1 = || { let ref t = x; t }; + //^^ impl Fn() -> &S + let c2 = || { let ref mut t = x; t }; + //^^ impl FnMut() -> &mut S + let c3 = || { let t = x; t }; + //^^ impl FnOnce() -> S +} + "#, + ) +} + +#[test] +fn capture_kinds_closure() { + check_types( + r#" +//- minicore: copy, fn +fn f() { + let mut x = 2; + x = 5; + let mut c1 = || { x = 3; x }; + //^^^^^^ impl FnMut() -> i32 + let mut c2 = || { c1() }; + //^^^^^^ impl FnMut() -> i32 + let mut c1 = || { x }; + //^^^^^^ impl Fn() -> i32 + let mut c2 = || { c1() }; + //^^^^^^ impl Fn() -> i32 + struct X; + let x = X; + let mut c1 = || { x }; + //^^^^^^ impl FnOnce() -> X + let mut c2 = || { c1() }; + //^^^^^^ impl FnOnce() -> X +} + "#, + ); +} + +#[test] +fn capture_kinds_overloaded_deref() { + check_types( + r#" +//- minicore: fn, deref_mut +use core::ops::{Deref, DerefMut}; + +struct Foo; +impl Deref for Foo { + type Target = (i32, u8); + fn deref(&self) -> &(i32, u8) { + &(5, 2) + } +} +impl DerefMut for Foo { + fn deref_mut(&mut self) -> &mut (i32, u8) { + &mut (5, 2) + } +} +fn test() { + let mut x = Foo; + let c1 = || *x; + //^^ impl Fn() -> (i32, u8) + let c2 = || { *x = (2, 5); }; + //^^ impl FnMut() + let c3 = || { x.1 }; + //^^ impl Fn() -> u8 + let c4 = || { x.1 = 6; }; + //^^ impl FnMut() +} + "#, + ); +} + +#[test] +fn capture_kinds_with_copy_types() { + check_types( + r#" +//- minicore: copy, clone, derive +#[derive(Clone, Copy)] +struct Copy; +struct NotCopy; +#[derive(Clone, Copy)] +struct Generic(T); + +trait Tr { + type Assoc; +} + +impl Tr for Copy { + type Assoc = NotCopy; +} + +#[derive(Clone, Copy)] +struct AssocGeneric(T::Assoc); + +fn f() { + let a = Copy; + let b = NotCopy; + let c = Generic(Copy); + let d = Generic(NotCopy); + let e: AssocGeneric = AssocGeneric(NotCopy); + let c1 = || a; + //^^ impl Fn() -> Copy + let c2 = || b; + //^^ impl FnOnce() -> NotCopy + let c3 = || c; + //^^ impl Fn() -> Generic + let c3 = || d; + //^^ impl FnOnce() -> Generic + let c3 = || e; + //^^ impl FnOnce() -> AssocGeneric +} + "#, + ) +} + +#[test] +fn derive_macro_should_work_for_associated_type() { + check_types( + r#" +//- minicore: copy, clone, derive +#[derive(Clone)] +struct X; +#[derive(Clone)] +struct Y; + +trait Tr { + type Assoc; +} + +impl Tr for X { + type Assoc = Y; +} + +#[derive(Clone)] +struct AssocGeneric(T::Assoc); + +fn f() { + let e: AssocGeneric = AssocGeneric(Y); + let e_clone = e.clone(); + //^^^^^^^ AssocGeneric +} + "#, + ) +} + #[test] fn cfgd_out_assoc_items() { check_types( @@ -2696,6 +2952,21 @@ fn f() { ) } +#[test] +fn infer_ref_to_raw_cast() { + check_types( + r#" +struct S; + +fn f() { + let s = &mut S; + let s = s as *mut _; + //^ *mut S +} + "#, + ); +} + #[test] fn infer_missing_type() { check_types( @@ -3258,35 +3529,60 @@ fn f(t: Ark) { ); } -// FIXME #[test] -fn castable_to2() { - check_infer( +fn const_dependent_on_local() { + check_types( r#" -fn func() { - let x = &0u32 as *const _; +fn main() { + let s = 5; + let t = [2; s]; + //^ [i32; _] } "#, - expect![[r#" - 10..44 '{ ...t _; }': () - 20..21 'x': *const {unknown} - 24..29 '&0u32': &u32 - 24..41 '&0u32 ...onst _': *const {unknown} - 25..29 '0u32': u32 - "#]], ); } #[test] fn issue_14275() { - // FIXME: evaluate const generic check_types( r#" struct Foo; fn main() { const B: bool = false; let foo = Foo::; - //^^^ Foo<_> + //^^^ Foo +} +"#, + ); + check_types( + r#" +struct Foo; +impl Foo { + fn foo(self) -> u8 { 2 } +} +impl Foo { + fn foo(self) -> u16 { 5 } +} +fn main() { + const B: bool = false; + let foo: Foo = Foo; + let x = foo.foo(); + //^ u16 +} +"#, + ); +} + +#[test] +fn cstring_literals() { + check_types( + r#" +#[lang = "CStr"] +pub struct CStr; + +fn main() { + c"ello"; + //^^^^^^^ &CStr } "#, ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index da76d7fd83f7..829a6ab189ec 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -90,7 +90,7 @@ fn infer_async_closure() { async fn test() { let f = async move |x: i32| x + 42; f; -// ^ |i32| -> impl Future +// ^ impl Fn(i32) -> impl Future let a = f(4); a; // ^ impl Future @@ -99,7 +99,7 @@ async fn test() { // ^ i32 let f = async move || 42; f; -// ^ || -> impl Future +// ^ impl Fn() -> impl Future let a = f(); a; // ^ impl Future @@ -116,7 +116,7 @@ async fn test() { }; let _: Option = c().await; c; -// ^ || -> impl Future> +// ^ impl Fn() -> impl Future> } "#, ); @@ -206,19 +206,27 @@ fn test() { fn infer_try_trait() { check_types( r#" -//- minicore: try, result +//- minicore: try, result, from fn test() { let r: Result = Result::Ok(1); let v = r?; v; } //^ i32 - -impl core::ops::Try for Result { - type Output = O; - type Error = Result; +"#, + ); } -impl> core::ops::FromResidual> for Result {} +#[test] +fn infer_try_block() { + // FIXME: We should test more cases, but it currently doesn't work, since + // our labeled block type inference is broken. + check_types( + r#" +//- minicore: try, option +fn test() { + let x: Option<_> = try { Some(2)?; }; + //^ Option<()> +} "#, ); } @@ -542,7 +550,7 @@ fn test() -> u64 { 53..54 'a': S 57..58 'S': S(fn(u32) -> u64) -> S 57..74 'S(|i| ...s u64)': S - 59..73 '|i| 2*i as u64': |u32| -> u64 + 59..73 '|i| 2*i as u64': impl Fn(u32) -> u64 60..61 'i': u32 63..64 '2': u64 63..73 '2*i as u64': u64 @@ -1325,9 +1333,9 @@ fn foo() -> (impl FnOnce(&str, T), impl Trait) { } "#, expect![[r#" - 134..165 '{ ...(C)) }': (|&str, T| -> (), Bar) - 140..163 '(|inpu...ar(C))': (|&str, T| -> (), Bar) - 141..154 '|input, t| {}': |&str, T| -> () + 134..165 '{ ...(C)) }': (impl Fn(&str, T), Bar) + 140..163 '(|inpu...ar(C))': (impl Fn(&str, T), Bar) + 141..154 '|input, t| {}': impl Fn(&str, T) 142..147 'input': &str 149..150 't': T 152..154 '{}': () @@ -1498,8 +1506,8 @@ fn main() { 71..105 '{ ...()); }': () 77..78 'f': fn f(&dyn Fn(S)) 77..102 'f(&|nu...foo())': () - 79..101 '&|numb....foo()': &|S| -> () - 80..101 '|numbe....foo()': |S| -> () + 79..101 '&|numb....foo()': &impl Fn(S) + 80..101 '|numbe....foo()': impl Fn(S) 81..87 'number': S 89..95 'number': S 89..101 'number.foo()': () @@ -1904,13 +1912,13 @@ fn make_foo_fn() -> Foo {} 131..132 'f': F 151..153 '{}': Lazy 251..497 '{ ...o(); }': () - 261..266 'lazy1': Lazy Foo> - 283..292 'Lazy::new': fn new Foo>(|| -> Foo) -> Lazy Foo> - 283..300 'Lazy::...| Foo)': Lazy Foo> - 293..299 '|| Foo': || -> Foo + 261..266 'lazy1': Lazy Foo> + 283..292 'Lazy::new': fn new Foo>(impl Fn() -> Foo) -> Lazy Foo> + 283..300 'Lazy::...| Foo)': Lazy Foo> + 293..299 '|| Foo': impl Fn() -> Foo 296..299 'Foo': Foo 310..312 'r1': usize - 315..320 'lazy1': Lazy Foo> + 315..320 'lazy1': Lazy Foo> 315..326 'lazy1.foo()': usize 368..383 'make_foo_fn_ptr': fn() -> Foo 399..410 'make_foo_fn': fn make_foo_fn() -> Foo @@ -1955,20 +1963,20 @@ fn test() { 163..167 '1u32': u32 174..175 'x': Option 174..190 'x.map(...v + 1)': Option - 180..189 '|v| v + 1': |u32| -> u32 + 180..189 '|v| v + 1': impl Fn(u32) -> u32 181..182 'v': u32 184..185 'v': u32 184..189 'v + 1': u32 188..189 '1': u32 196..197 'x': Option 196..212 'x.map(... 1u64)': Option - 202..211 '|_v| 1u64': |u32| -> u64 + 202..211 '|_v| 1u64': impl Fn(u32) -> u64 203..205 '_v': u32 207..211 '1u64': u64 222..223 'y': Option 239..240 'x': Option 239..252 'x.map(|_v| 1)': Option - 245..251 '|_v| 1': |u32| -> i64 + 245..251 '|_v| 1': impl Fn(u32) -> i64 246..248 '_v': u32 250..251 '1': i64 "#]], @@ -1997,11 +2005,11 @@ fn test u64>(f: F) { //^^^^ u64 let g = |v| v + 1; //^^^^^ u64 - //^^^^^^^^^ |u64| -> u64 + //^^^^^^^^^ impl Fn(u64) -> u64 g(1u64); //^^^^^^^ u64 let h = |v| 1u128 + v; - //^^^^^^^^^^^^^ |u128| -> u128 + //^^^^^^^^^^^^^ impl Fn(u128) -> u128 }"#, ); } @@ -2054,17 +2062,17 @@ fn test() { 312..314 '{}': () 330..489 '{ ... S); }': () 340..342 'x1': u64 - 345..349 'foo1': fn foo1 u64>(S, |S| -> u64) -> u64 + 345..349 'foo1': fn foo1 u64>(S, impl Fn(S) -> u64) -> u64 345..368 'foo1(S...hod())': u64 350..351 'S': S - 353..367 '|s| s.method()': |S| -> u64 + 353..367 '|s| s.method()': impl Fn(S) -> u64 354..355 's': S 357..358 's': S 357..367 's.method()': u64 378..380 'x2': u64 - 383..387 'foo2': fn foo2 u64>(|S| -> u64, S) -> u64 + 383..387 'foo2': fn foo2 u64>(impl Fn(S) -> u64, S) -> u64 383..406 'foo2(|...(), S)': u64 - 388..402 '|s| s.method()': |S| -> u64 + 388..402 '|s| s.method()': impl Fn(S) -> u64 389..390 's': S 392..393 's': S 392..402 's.method()': u64 @@ -2073,14 +2081,14 @@ fn test() { 421..422 'S': S 421..446 'S.foo1...hod())': u64 428..429 'S': S - 431..445 '|s| s.method()': |S| -> u64 + 431..445 '|s| s.method()': impl Fn(S) -> u64 432..433 's': S 435..436 's': S 435..445 's.method()': u64 456..458 'x4': u64 461..462 'S': S 461..486 'S.foo2...(), S)': u64 - 468..482 '|s| s.method()': |S| -> u64 + 468..482 '|s| s.method()': impl Fn(S) -> u64 469..470 's': S 472..473 's': S 472..482 's.method()': u64 @@ -2554,9 +2562,9 @@ fn main() { 72..74 '_v': F 117..120 '{ }': () 132..163 '{ ... }); }': () - 138..148 'f::<(), _>': fn f<(), |&()| -> ()>(|&()| -> ()) + 138..148 'f::<(), _>': fn f<(), impl Fn(&())>(impl Fn(&())) 138..160 'f::<()... z; })': () - 149..159 '|z| { z; }': |&()| -> () + 149..159 '|z| { z; }': impl Fn(&()) 150..151 'z': &() 153..159 '{ z; }': () 155..156 'z': &() @@ -2713,9 +2721,9 @@ fn main() { 983..998 'Vec::::new': fn new() -> Vec 983..1000 'Vec::<...:new()': Vec 983..1012 'Vec::<...iter()': IntoIter - 983..1075 'Vec::<...one })': FilterMap, |i32| -> Option> + 983..1075 'Vec::<...one })': FilterMap, impl Fn(i32) -> Option> 983..1101 'Vec::<... y; })': () - 1029..1074 '|x| if...None }': |i32| -> Option + 1029..1074 '|x| if...None }': impl Fn(i32) -> Option 1030..1031 'x': i32 1033..1074 'if x >...None }': Option 1036..1037 'x': i32 @@ -2728,7 +2736,7 @@ fn main() { 1049..1057 'x as u32': u32 1066..1074 '{ None }': Option 1068..1072 'None': Option - 1090..1100 '|y| { y; }': |u32| -> () + 1090..1100 '|y| { y; }': impl Fn(u32) 1091..1092 'y': u32 1094..1100 '{ y; }': () 1096..1097 'y': u32 @@ -2971,13 +2979,13 @@ fn foo() { 52..126 '{ ...)(s) }': () 62..63 's': Option 66..78 'Option::None': Option - 88..89 'f': |Option| -> () - 92..111 '|x: Op...2>| {}': |Option| -> () + 88..89 'f': impl Fn(Option) + 92..111 '|x: Op...2>| {}': impl Fn(Option) 93..94 'x': Option 109..111 '{}': () 117..124 '(&f)(s)': () - 118..120 '&f': &|Option| -> () - 119..120 'f': |Option| -> () + 118..120 '&f': &impl Fn(Option) + 119..120 'f': impl Fn(Option) 122..123 's': Option "#]], ); @@ -3043,7 +3051,7 @@ impl core::ops::Deref for Box { type Target = T; fn deref(&self) -> &T { - &self.inner + unsafe { &*self.inner } } } @@ -3054,23 +3062,25 @@ fn foo() { }"#, expect![[r#" 154..158 'self': &Box - 166..193 '{ ... }': &T - 176..187 '&self.inner': &*mut T - 177..181 'self': &Box - 177..187 'self.inner': *mut T - 206..296 '{ ...&s); }': () - 216..217 's': Option - 220..224 'None': Option - 234..235 'f': Box)> - 269..282 'box (|ps| {})': Box<|&Option| -> ()> - 274..281 '|ps| {}': |&Option| -> () - 275..277 'ps': &Option - 279..281 '{}': () - 288..289 'f': Box)> - 288..293 'f(&s)': () - 290..292 '&s': &Option - 291..292 's': Option - 269..282: expected Box)>, got Box<|&Option| -> ()> + 166..205 '{ ... }': &T + 176..199 'unsafe...nner }': &T + 185..197 '&*self.inner': &T + 186..197 '*self.inner': T + 187..191 'self': &Box + 187..197 'self.inner': *mut T + 218..308 '{ ...&s); }': () + 228..229 's': Option + 232..236 'None': Option + 246..247 'f': Box)> + 281..294 'box (|ps| {})': Box)> + 286..293 '|ps| {}': impl Fn(&Option) + 287..289 'ps': &Option + 291..293 '{}': () + 300..301 'f': Box)> + 300..305 'f(&s)': () + 302..304 '&s': &Option + 303..304 's': Option + 281..294: expected Box)>, got Box)> "#]], ); } @@ -3709,7 +3719,6 @@ async fn get_accounts() -> Result { #[test] fn local_impl_1() { - check!(block_local_impls); check_types( r#" trait Trait { @@ -3731,7 +3740,6 @@ fn foo(&self) -> u32 { 0 } #[test] fn local_impl_2() { - check!(block_local_impls); check_types( r#" struct S; @@ -3753,7 +3761,6 @@ fn foo(&self) -> u32 { 0 } #[test] fn local_impl_3() { - check!(block_local_impls); check_types( r#" trait Trait { @@ -3777,6 +3784,62 @@ fn foo(&self) -> S1 { S1 } ); } +#[test] +fn foreign_trait_with_local_trait_impl() { + check!(block_local_impls); + check( + r#" +mod module { + pub trait T { + const C: usize; + fn f(&self); + } +} + +fn f() { + use module::T; + impl T for usize { + const C: usize = 0; + fn f(&self) {} + } + 0usize.f(); + //^^^^^^^^^^ type: () + usize::C; + //^^^^^^^^type: usize +} +"#, + ); +} + +#[test] +fn regression_14443_trait_solve() { + check_no_mismatches( + r#" +trait T { + fn f(&self) {} +} + + +fn main() { + struct A; + impl T for A {} + + let a = A; + + let b = { + struct B; + impl T for B {} + + B + }; + + a.f(); + b.f(); +} +"#, + ) +} + #[test] fn associated_type_sized_bounds() { check_infer( @@ -4149,3 +4212,201 @@ fn test() { "#, ); } + +#[test] +fn associated_type_in_struct_expr_path() { + // FIXME: All annotation should be resolvable. + // For lines marked as unstable, see rust-lang/rust#86935. + // FIXME: Remove the comments once stablized. + check_types( + r#" +trait Trait { + type Assoc; + fn f(); +} + +struct S { x: u32 } + +impl Trait for () { + type Assoc = S; + + fn f() { + let x = 42; + let a = Self::Assoc { x }; + // ^ S + let a = ::Assoc { x }; // unstable + // ^ {unknown} + + // should be `Copy` but we don't track ownership anyway. + let value = S { x }; + if let Self::Assoc { x } = value {} + // ^ u32 + if let ::Assoc { x } = value {} // unstable + // ^ {unknown} + } +} + "#, + ); +} + +#[test] +fn associated_type_in_struct_expr_path_enum() { + // FIXME: All annotation should be resolvable. + // For lines marked as unstable, see rust-lang/rust#86935. + // FIXME: Remove the comments once stablized. + check_types( + r#" +trait Trait { + type Assoc; + fn f(); +} + +enum E { + Unit, + Struct { x: u32 }, +} + +impl Trait for () { + type Assoc = E; + + fn f() { + let a = Self::Assoc::Unit; + // ^ E + let a = ::Assoc::Unit; + // ^ E + let a = ::Unit; + // ^ E + let a = <::Assoc>::Unit; + // ^ E + + // should be `Copy` but we don't track ownership anyway. + let value = E::Unit; + if let Self::Assoc::Unit = value {} + // ^^^^^^^^^^^^^^^^^ E + if let ::Assoc::Unit = value {} + // ^^^^^^^^^^^^^^^^^^^ E + if let ::Unit = value {} + // ^^^^^^^^^^^^^^^^^^^ E + if let <::Assoc>::Unit = value {} + // ^^^^^^^^^^^^^^^^^^^^^ E + + let x = 42; + let a = Self::Assoc::Struct { x }; + // ^ E + let a = ::Assoc::Struct { x }; // unstable + // ^ {unknown} + let a = ::Struct { x }; // unstable + // ^ {unknown} + let a = <::Assoc>::Struct { x }; // unstable + // ^ {unknown} + + // should be `Copy` but we don't track ownership anyway. + let value = E::Struct { x: 42 }; + if let Self::Assoc::Struct { x } = value {} + // ^ u32 + if let ::Assoc::Struct { x } = value {} // unstable + // ^ {unknown} + if let ::Struct { x } = value {} // unstable + // ^ {unknown} + if let <::Assoc>::Struct { x } = value {} // unstable + // ^ {unknown} + } +} + "#, + ); +} + +#[test] +fn derive_macro_bounds() { + check_types( + r#" + //- minicore: clone, derive + #[derive(Clone)] + struct Copy; + struct NotCopy; + #[derive(Clone)] + struct Generic(T); + trait Tr { + type Assoc; + } + impl Tr for Copy { + type Assoc = NotCopy; + } + #[derive(Clone)] + struct AssocGeneric(T::Assoc); + + #[derive(Clone)] + struct AssocGeneric2(::Assoc); + + #[derive(Clone)] + struct AssocGeneric3(Generic); + + #[derive(Clone)] + struct Vec(); + + #[derive(Clone)] + struct R1(Vec); + #[derive(Clone)] + struct R2(R1); + + fn f() { + let x = (&Copy).clone(); + //^ Copy + let x = (&NotCopy).clone(); + //^ &NotCopy + let x = (&Generic(Copy)).clone(); + //^ Generic + let x = (&Generic(NotCopy)).clone(); + //^ &Generic + let x: &AssocGeneric = &AssocGeneric(NotCopy); + let x = x.clone(); + //^ &AssocGeneric + let x: &AssocGeneric2 = &AssocGeneric2(NotCopy); + let x = x.clone(); + //^ &AssocGeneric2 + let x: &AssocGeneric3 = &AssocGeneric3(Generic(NotCopy)); + let x = x.clone(); + //^ &AssocGeneric3 + let x = (&R1(Vec())).clone(); + //^ R1 + let x = (&R2(R1(Vec()))).clone(); + //^ R2 + } + "#, + ); +} + +#[test] +fn trait_obligations_should_be_registered_during_path_inference() { + check_types( + r#" +//- minicore: fn, from +struct S(T); +fn map S>(_: T, _: F) -> U { loop {} } + +fn test(v: S) { + let res = map(v, Into::into); + //^^^ i32 +} +"#, + ); +} + +#[test] +fn fn_obligation_should_be_registered_during_path_inference() { + check_types( + r#" +//- minicore: fn, from +struct S(T); +impl S { + fn foo>>(_: U) -> Self { loop {} } +} +fn map U>(_: T, _: F) -> U { loop {} } + +fn test(v: S) { + let res = map(v, S::foo); + //^^^ S +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tls.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tls.rs index b7e6ee6740be..83814ed0ec1f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tls.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tls.rs @@ -24,7 +24,8 @@ pub(crate) fn debug_struct_id( AdtId::UnionId(it) => self.0.union_data(it).name.clone(), AdtId::EnumId(it) => self.0.enum_data(it).name.clone(), }; - name.fmt(f) + name.display(self.0.upcast()).fmt(f)?; + Ok(()) } pub(crate) fn debug_trait_id( @@ -34,7 +35,8 @@ pub(crate) fn debug_trait_id( ) -> Result<(), fmt::Error> { let trait_: hir_def::TraitId = from_chalk_trait_id(id); let trait_data = self.0.trait_data(trait_); - trait_data.name.fmt(f) + trait_data.name.display(self.0.upcast()).fmt(f)?; + Ok(()) } pub(crate) fn debug_assoc_type_id( @@ -49,7 +51,13 @@ pub(crate) fn debug_assoc_type_id( _ => panic!("associated type not in trait"), }; let trait_data = self.0.trait_data(trait_); - write!(fmt, "{}::{}", trait_data.name, type_alias_data.name) + write!( + fmt, + "{}::{}", + trait_data.name.display(self.0.upcast()), + type_alias_data.name.display(self.0.upcast()) + )?; + Ok(()) } pub(crate) fn debug_projection_ty( @@ -67,7 +75,7 @@ pub(crate) fn debug_projection_ty( let trait_ref = projection_ty.trait_ref(self.0); let trait_params = trait_ref.substitution.as_slice(Interner); let self_ty = trait_ref.self_type_parameter(Interner); - write!(fmt, "<{self_ty:?} as {trait_name}")?; + write!(fmt, "<{self_ty:?} as {}", trait_name.display(self.0.upcast()))?; if trait_params.len() > 1 { write!( fmt, @@ -75,7 +83,7 @@ pub(crate) fn debug_projection_ty( trait_params[1..].iter().format_with(", ", |x, f| f(&format_args!("{x:?}"))), )?; } - write!(fmt, ">::{}", type_alias_data.name)?; + write!(fmt, ">::{}", type_alias_data.name.display(self.0.upcast()))?; let proj_params_count = projection_ty.substitution.len(Interner) - trait_params.len(); let proj_params = &projection_ty.substitution.as_slice(Interner)[..proj_params_count]; @@ -105,9 +113,9 @@ pub(crate) fn debug_fn_def_id( } }; match def { - CallableDefId::FunctionId(_) => write!(fmt, "{{fn {name}}}"), + CallableDefId::FunctionId(_) => write!(fmt, "{{fn {}}}", name.display(self.0.upcast())), CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_) => { - write!(fmt, "{{ctor {name}}}") + write!(fmt, "{{ctor {}}}", name.display(self.0.upcast())) } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs index 3ab85c68f5b9..f40b7db3a551 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs @@ -1,22 +1,24 @@ //! Trait solving using Chalk. -use std::{env::var, sync::Arc}; +use std::env::var; -use chalk_ir::GoalData; +use chalk_ir::{fold::TypeFoldable, DebruijnIndex, GoalData}; use chalk_recursive::Cache; -use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; +use chalk_solve::{logging_db::LoggingRustIrDatabase, rust_ir, Solver}; use base_db::CrateId; use hir_def::{ lang_item::{LangItem, LangItemTarget}, - TraitId, + BlockId, TraitId, }; +use hir_expand::name::{name, Name}; use stdx::panic_context; +use triomphe::Arc; use crate::{ - db::HirDatabase, infer::unify::InferenceTable, AliasEq, AliasTy, Canonical, DomainGoal, Goal, - Guidance, InEnvironment, Interner, ProjectionTy, ProjectionTyExt, Solution, TraitRefExt, Ty, - TyKind, WhereClause, + db::HirDatabase, infer::unify::InferenceTable, utils::UnevaluatedConstEvaluatorFolder, AliasEq, + AliasTy, Canonical, DomainGoal, Goal, Guidance, InEnvironment, Interner, ProjectionTy, + ProjectionTyExt, Solution, TraitRefExt, Ty, TyKind, WhereClause, }; /// This controls how much 'time' we give the Chalk solver before giving up. @@ -26,6 +28,7 @@ pub(crate) struct ChalkContext<'a> { pub(crate) db: &'a dyn HirDatabase, pub(crate) krate: CrateId, + pub(crate) block: Option, } fn create_chalk_solver() -> chalk_recursive::RecursiveSolver { @@ -43,6 +46,7 @@ fn create_chalk_solver() -> chalk_recursive::RecursiveSolver { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TraitEnvironment { pub krate: CrateId, + pub block: Option, // FIXME make this a BTreeMap pub(crate) traits_from_clauses: Vec<(Ty, TraitId)>, pub env: chalk_ir::Environment, @@ -52,6 +56,7 @@ impl TraitEnvironment { pub fn empty(krate: CrateId) -> Self { TraitEnvironment { krate, + block: None, traits_from_clauses: Vec::new(), env: chalk_ir::Environment::new(Interner), } @@ -78,11 +83,12 @@ pub(crate) fn normalize_projection_query( pub(crate) fn trait_solve_query( db: &dyn HirDatabase, krate: CrateId, + block: Option, goal: Canonical>, ) -> Option { let _p = profile::span("trait_solve_query").detail(|| match &goal.value.goal.data(Interner) { GoalData::DomainGoal(DomainGoal::Holds(WhereClause::Implemented(it))) => { - db.trait_data(it.hir_trait_id()).name.to_string() + db.trait_data(it.hir_trait_id()).name.display(db.upcast()).to_string() } GoalData::DomainGoal(DomainGoal::Holds(WhereClause::AliasEq(_))) => "alias_eq".to_string(), _ => "??".to_string(), @@ -100,18 +106,25 @@ pub(crate) fn trait_solve_query( } } + // Chalk see `UnevaluatedConst` as a unique concrete value, but we see it as an alias for another const. So + // we should get rid of it when talking to chalk. + let goal = goal + .try_fold_with(&mut UnevaluatedConstEvaluatorFolder { db }, DebruijnIndex::INNERMOST) + .unwrap(); + // We currently don't deal with universes (I think / hope they're not yet // relevant for our use cases?) let u_canonical = chalk_ir::UCanonical { canonical: goal, universes: 1 }; - solve(db, krate, &u_canonical) + solve(db, krate, block, &u_canonical) } fn solve( db: &dyn HirDatabase, krate: CrateId, + block: Option, goal: &chalk_ir::UCanonical>>, ) -> Option> { - let context = ChalkContext { db, krate }; + let context = ChalkContext { db, krate, block }; tracing::debug!("solve goal: {:?}", goal); let mut solver = create_chalk_solver(); @@ -171,8 +184,10 @@ fn is_chalk_print() -> bool { std::env::var("CHALK_PRINT").is_ok() } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum FnTrait { + // Warning: Order is important. If something implements `x` it should also implement + // `y` if `y <= x`. FnOnce, FnMut, Fn, @@ -187,7 +202,23 @@ const fn lang_item(self) -> LangItem { } } - pub fn get_id(&self, db: &dyn HirDatabase, krate: CrateId) -> Option { + pub const fn to_chalk_ir(self) -> rust_ir::ClosureKind { + match self { + FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce, + FnTrait::FnMut => rust_ir::ClosureKind::FnMut, + FnTrait::Fn => rust_ir::ClosureKind::Fn, + } + } + + pub fn method_name(self) -> Name { + match self { + FnTrait::FnOnce => name!(call_once), + FnTrait::FnMut => name!(call_mut), + FnTrait::Fn => name!(call), + } + } + + pub fn get_id(self, db: &dyn HirDatabase, krate: CrateId) -> Option { let target = db.lang_item(krate, self.lang_item())?; match target { LangItemTarget::Trait(t) => Some(t), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs index 34d957e26ef5..681d087ede6e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs @@ -4,7 +4,11 @@ use std::iter; use base_db::CrateId; -use chalk_ir::{cast::Cast, fold::Shift, BoundVar, DebruijnIndex}; +use chalk_ir::{ + cast::Cast, + fold::{FallibleTypeFolder, Shift}, + BoundVar, DebruijnIndex, +}; use either::Either; use hir_def::{ db::DefDatabase, @@ -15,16 +19,22 @@ lang_item::LangItem, resolver::{HasResolver, TypeNs}, type_ref::{TraitBoundModifier, TypeRef}, - ConstParamId, FunctionId, GenericDefId, ItemContainerId, Lookup, TraitId, TypeAliasId, - TypeOrConstParamId, TypeParamId, + ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId, ItemContainerId, + LocalEnumVariantId, Lookup, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, }; use hir_expand::name::Name; use intern::Interned; use rustc_hash::FxHashSet; use smallvec::{smallvec, SmallVec}; +use stdx::never; use crate::{ - db::HirDatabase, ChalkTraitId, Interner, Substitution, TraitRef, TraitRefExt, WhereClause, + consteval::unknown_const, + db::HirDatabase, + layout::{Layout, TagEncoding}, + mir::pad16, + ChalkTraitId, Const, ConstScalar, GenericArg, Interner, Substitution, TraitRef, TraitRefExt, + Ty, WhereClause, }; pub(crate) fn fn_traits( @@ -69,9 +79,7 @@ pub(super) fn all_super_trait_refs( cb: impl FnMut(TraitRef) -> Option, ) -> Option { let seen = iter::once(trait_ref.trait_id).collect(); - let mut stack = Vec::new(); - stack.push(trait_ref); - SuperTraits { db, seen, stack }.find_map(cb) + SuperTraits { db, seen, stack: vec![trait_ref] }.find_map(cb) } struct SuperTraits<'a> { @@ -130,7 +138,7 @@ fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(Tra WherePredicate::Lifetime { .. } => None, }) .filter(|(_, bound_modifier)| matches!(bound_modifier, TraitBoundModifier::None)) - .filter_map(|(path, _)| match resolver.resolve_path_in_type_ns_fully(db, path.mod_path()) { + .filter_map(|(path, _)| match resolver.resolve_path_in_type_ns_fully(db, path) { Some(TypeNs::TraitId(t)) => Some(t), _ => None, }) @@ -176,6 +184,37 @@ pub(crate) fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics { Generics { def, params: db.generic_params(def), parent_generics } } +/// It is a bit different from the rustc equivalent. Currently it stores: +/// - 0: the function signature, encoded as a function pointer type +/// - 1..n: generics of the parent +/// +/// and it doesn't store the closure types and fields. +/// +/// Codes should not assume this ordering, and should always use methods available +/// on this struct for retriving, and `TyBuilder::substs_for_closure` for creating. +pub(crate) struct ClosureSubst<'a>(pub(crate) &'a Substitution); + +impl<'a> ClosureSubst<'a> { + pub(crate) fn parent_subst(&self) -> &'a [GenericArg] { + match self.0.as_slice(Interner) { + [_, x @ ..] => x, + _ => { + never!("Closure missing parameter"); + &[] + } + } + } + + pub(crate) fn sig_ty(&self) -> &'a Ty { + match self.0.as_slice(Interner) { + [x, ..] => x.assert_ty_ref(Interner), + _ => { + unreachable!("Closure missing sig_ty parameter"); + } + } + } +} + #[derive(Debug)] pub(crate) struct Generics { def: GenericDefId, @@ -354,3 +393,74 @@ pub fn is_fn_unsafe_to_call(db: &dyn HirDatabase, func: FunctionId) -> bool { _ => false, } } + +pub(crate) struct UnevaluatedConstEvaluatorFolder<'a> { + pub(crate) db: &'a dyn HirDatabase, +} + +impl FallibleTypeFolder for UnevaluatedConstEvaluatorFolder<'_> { + type Error = (); + + fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn try_fold_const( + &mut self, + constant: Const, + _outer_binder: DebruijnIndex, + ) -> Result { + if let chalk_ir::ConstValue::Concrete(c) = &constant.data(Interner).value { + if let ConstScalar::UnevaluatedConst(id, subst) = &c.interned { + if let Ok(eval) = self.db.const_eval(*id, subst.clone()) { + return Ok(eval); + } else { + return Ok(unknown_const(constant.data(Interner).ty.clone())); + } + } + } + Ok(constant) + } +} + +pub(crate) fn detect_variant_from_bytes<'a>( + layout: &'a Layout, + db: &dyn HirDatabase, + krate: CrateId, + b: &[u8], + e: EnumId, +) -> Option<(LocalEnumVariantId, &'a Layout)> { + let (var_id, var_layout) = match &layout.variants { + hir_def::layout::Variants::Single { index } => (index.0, &*layout), + hir_def::layout::Variants::Multiple { tag, tag_encoding, variants, .. } => { + let target_data_layout = db.target_data_layout(krate)?; + let size = tag.size(&*target_data_layout).bytes_usize(); + let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field + let tag = i128::from_le_bytes(pad16(&b[offset..offset + size], false)); + match tag_encoding { + TagEncoding::Direct => { + let x = variants.iter_enumerated().find(|x| { + db.const_eval_discriminant(EnumVariantId { parent: e, local_id: x.0 .0 }) + == Ok(tag) + })?; + (x.0 .0, x.1) + } + TagEncoding::Niche { untagged_variant, niche_start, .. } => { + let candidate_tag = tag.wrapping_sub(*niche_start as i128) as usize; + let variant = variants + .iter_enumerated() + .map(|(x, _)| x) + .filter(|x| x != untagged_variant) + .nth(candidate_tag) + .unwrap_or(*untagged_variant); + (variant.0, &variants[variant]) + } + } + } + }; + Some((var_id, var_layout)) +} diff --git a/src/tools/rust-analyzer/crates/hir/Cargo.toml b/src/tools/rust-analyzer/crates/hir/Cargo.toml index ef40a8902d73..a20aff93f19b 100644 --- a/src/tools/rust-analyzer/crates/hir/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir/Cargo.toml @@ -17,6 +17,7 @@ either = "1.7.0" arrayvec = "0.7.2" itertools = "0.10.5" smallvec.workspace = true +triomphe.workspace = true once_cell = "1.17.0" # local deps diff --git a/src/tools/rust-analyzer/crates/hir/src/attrs.rs b/src/tools/rust-analyzer/crates/hir/src/attrs.rs index db0b84ef0887..b817937296d2 100644 --- a/src/tools/rust-analyzer/crates/hir/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir/src/attrs.rs @@ -4,7 +4,6 @@ attr::{AttrsWithOwner, Documentation}, item_scope::ItemInNs, path::ModPath, - per_ns::PerNs, resolver::HasResolver, AttrDefId, GenericParamId, ModuleDefId, }; @@ -41,7 +40,7 @@ macro_rules! impl_has_attrs { impl HasAttrs for $def { fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { let def = AttrDefId::$def_id(self.into()); - db.attrs(def) + db.attrs_with_owner(def) } fn docs(self, db: &dyn HirDatabase) -> Option { let def = AttrDefId::$def_id(self.into()); @@ -121,6 +120,7 @@ fn resolve_doc_path( } } +/// Resolves the item `link` points to in the scope of `def`. fn resolve_doc_path( db: &dyn HirDatabase, def: AttrDefId, @@ -155,14 +155,14 @@ fn resolve_doc_path( .syntax_node() .descendants() .find_map(ast::Path::cast)?; - if ast_path.to_string() != link { + if ast_path.syntax().text() != link { return None; } ModPath::from_src(db.upcast(), ast_path, &Hygiene::new_unhygienic())? }; let resolved = resolver.resolve_module_path_in_items(db.upcast(), &modpath); - let resolved = if resolved == PerNs::none() { + let resolved = if resolved.is_none() { resolver.resolve_module_path_in_trait_assoc_items(db.upcast(), &modpath)? } else { resolved diff --git a/src/tools/rust-analyzer/crates/hir/src/db.rs b/src/tools/rust-analyzer/crates/hir/src/db.rs index 0935b5ea5194..e0cde689fed8 100644 --- a/src/tools/rust-analyzer/crates/hir/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir/src/db.rs @@ -6,8 +6,8 @@ pub use hir_def::db::*; pub use hir_expand::db::{ AstIdMapQuery, ExpandDatabase, ExpandDatabaseStorage, ExpandProcMacroQuery, HygieneFrameQuery, - InternMacroCallQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandErrorQuery, - MacroExpandQuery, ParseMacroExpansionQuery, + InternMacroCallQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, + ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, }; pub use hir_ty::db::*; diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 253d62dafc60..b64d81490bb1 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -10,7 +10,7 @@ use either::Either; use hir_def::path::ModPath; use hir_expand::{name::Name, HirFileId, InFile}; -use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; +use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange}; use crate::{AssocItem, Field, Local, MacroKind, Type}; @@ -38,19 +38,25 @@ fn from(d: $diag) -> AnyDiagnostic { IncorrectCase, InvalidDeriveTarget, IncoherentImpl, + MacroDefError, MacroError, + MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe, + MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap, + TypedHole, TypeMismatch, + UndeclaredLabel, UnimplementedBuiltinMacro, + UnreachableLabel, UnresolvedExternCrate, UnresolvedField, UnresolvedImport, @@ -61,6 +67,19 @@ fn from(d: $diag) -> AnyDiagnostic { UnusedMut, ]; +#[derive(Debug)] +pub struct BreakOutsideOfLoop { + pub expr: InFile>, + pub is_break: bool, + pub bad_value_break: bool, +} + +#[derive(Debug)] +pub struct TypedHole { + pub expr: InFile>, + pub expected: Type, +} + #[derive(Debug)] pub struct UnresolvedModule { pub decl: InFile>, @@ -84,6 +103,17 @@ pub struct UnresolvedMacroCall { pub path: ModPath, pub is_bang: bool, } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct UnreachableLabel { + pub node: InFile>, + pub name: Name, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct UndeclaredLabel { + pub node: InFile>, + pub name: Name, +} #[derive(Debug, Clone, Eq, PartialEq)] pub struct InactiveCode { @@ -111,6 +141,20 @@ pub struct MacroError { pub message: String, } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MacroExpansionParseError { + pub node: InFile, + pub precise_location: Option, + pub errors: Box<[SyntaxError]>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MacroDefError { + pub node: InFile>, + pub message: String, + pub name: Option, +} + #[derive(Debug)] pub struct UnimplementedBuiltinMacro { pub node: InFile, @@ -166,13 +210,6 @@ pub struct PrivateField { pub field: Field, } -#[derive(Debug)] -pub struct BreakOutsideOfLoop { - pub expr: InFile>, - pub is_break: bool, - pub bad_value_break: bool, -} - #[derive(Debug)] pub struct MissingUnsafe { pub expr: InFile>, @@ -223,3 +260,9 @@ pub struct NeedMut { pub struct UnusedMut { pub local: Local, } + +#[derive(Debug)] +pub struct MovedOutOfRef { + pub ty: Type, + pub span: InFile, +} diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 5aae92efd19e..9a2090ab79a2 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -1,6 +1,6 @@ //! HirDisplay implementations for various hir types. use hir_def::{ - adt::VariantData, + data::adt::VariantData, generics::{ TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget, }, @@ -8,6 +8,7 @@ type_ref::{TypeBound, TypeRef}, AdtId, GenericDefId, }; +use hir_expand::name; use hir_ty::{ display::{ write_bounds_like_dyn_trait_with_prefix, write_visibility, HirDisplay, HirDisplayError, @@ -50,7 +51,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { // FIXME: String escape? write!(f, "extern \"{}\" ", &**abi)?; } - write!(f, "fn {}", data.name)?; + write!(f, "fn {}", data.name.display(f.db.upcast()))?; write_generic_params(GenericDefId::FunctionId(self.id), f)?; @@ -62,7 +63,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { { f.write_char('&')?; if let Some(lifetime) = lifetime { - write!(f, "{} ", lifetime.name)?; + write!(f, "{} ", lifetime.name.display(f.db.upcast()))?; } if let hir_def::type_ref::Mutability::Mut = mut_ { f.write_str("mut ")?; @@ -76,22 +77,22 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { }; let mut first = true; - for (name, type_ref) in &data.params { + // FIXME: Use resolved `param.ty` once we no longer discard lifetimes + for (type_ref, param) in data.params.iter().zip(self.assoc_fn_params(db)) { + let local = param.as_local(db).map(|it| it.name(db)); if !first { f.write_str(", ")?; } else { first = false; - if data.has_self_param() { + if local == Some(name!(self)) { write_self_param(type_ref, f)?; continue; } } - match name { - Some(name) => write!(f, "{name}: ")?, + match local { + Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?, None => f.write_str("_: ")?, } - // FIXME: Use resolved `param.ty` or raw `type_ref`? - // The former will ignore lifetime arguments currently. type_ref.hir_fmt(f)?; } @@ -150,7 +151,7 @@ impl HirDisplay for Struct { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; f.write_str("struct ")?; - write!(f, "{}", self.name(f.db))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; let def_id = GenericDefId::AdtId(AdtId::StructId(self.id)); write_generic_params(def_id, f)?; write_where_clause(def_id, f)?; @@ -162,7 +163,7 @@ impl HirDisplay for Enum { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; f.write_str("enum ")?; - write!(f, "{}", self.name(f.db))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; let def_id = GenericDefId::AdtId(AdtId::EnumId(self.id)); write_generic_params(def_id, f)?; write_where_clause(def_id, f)?; @@ -174,7 +175,7 @@ impl HirDisplay for Union { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; f.write_str("union ")?; - write!(f, "{}", self.name(f.db))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; let def_id = GenericDefId::AdtId(AdtId::UnionId(self.id)); write_generic_params(def_id, f)?; write_where_clause(def_id, f)?; @@ -185,14 +186,14 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { impl HirDisplay for Field { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.parent.module(f.db).id, self.visibility(f.db), f)?; - write!(f, "{}: ", self.name(f.db))?; + write!(f, "{}: ", self.name(f.db).display(f.db.upcast()))?; self.ty(f.db).hir_fmt(f) } } impl HirDisplay for Variant { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "{}", self.name(f.db))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; let data = self.variant_data(f.db); match &*data { VariantData::Unit => {} @@ -221,7 +222,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { f.write_str(", ")?; } // Enum variant fields must be pub. - write!(f, "{}: ", field.name)?; + write!(f, "{}: ", field.name.display(f.db.upcast()))?; field.type_ref.hir_fmt(f)?; } f.write_str(" }")?; @@ -258,7 +259,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { impl HirDisplay for TypeParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "{}", self.name(f.db))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; if f.omit_verbose_types() { return Ok(()); } @@ -285,13 +286,13 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { impl HirDisplay for LifetimeParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "{}", self.name(f.db)) + write!(f, "{}", self.name(f.db).display(f.db.upcast())) } } impl HirDisplay for ConstParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "const {}: ", self.name(f.db))?; + write!(f, "const {}: ", self.name(f.db).display(f.db.upcast()))?; self.ty(f.db).hir_fmt(f) } } @@ -324,7 +325,7 @@ fn write_generic_params( }; for (_, lifetime) in params.lifetimes.iter() { delim(f)?; - write!(f, "{}", lifetime.name)?; + write!(f, "{}", lifetime.name.display(f.db.upcast()))?; } for (_, ty) in params.type_or_consts.iter() { if let Some(name) = &ty.name() { @@ -334,7 +335,7 @@ fn write_generic_params( continue; } delim(f)?; - write!(f, "{name}")?; + write!(f, "{}", name.display(f.db.upcast()))?; if let Some(default) = &ty.default { f.write_str(" = ")?; default.hir_fmt(f)?; @@ -342,7 +343,7 @@ fn write_generic_params( } TypeOrConstParamData::ConstParamData(c) => { delim(f)?; - write!(f, "const {name}: ")?; + write!(f, "const {}: ", name.display(f.db.upcast()))?; c.ty.hir_fmt(f)?; } } @@ -379,7 +380,7 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), WherePredicateTypeTarget::TypeRef(ty) => ty.hir_fmt(f), WherePredicateTypeTarget::TypeOrConstParam(id) => { match ¶ms.type_or_consts[*id].name() { - Some(name) => write!(f, "{name}"), + Some(name) => write!(f, "{}", name.display(f.db.upcast())), None => f.write_str("{unnamed}"), } } @@ -411,10 +412,15 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), WherePredicate::Lifetime { target, bound } => { if matches!(prev_pred, Some(WherePredicate::Lifetime { target: target_, .. }) if target_ == target) { - write!(f, " + {}", bound.name)?; + write!(f, " + {}", bound.name.display(f.db.upcast()))?; } else { new_predicate(f)?; - write!(f, "{}: {}", target.name, bound.name)?; + write!( + f, + "{}: {}", + target.name.display(f.db.upcast()), + bound.name.display(f.db.upcast()) + )?; } } WherePredicate::ForLifetime { lifetimes, target, bound } => { @@ -431,7 +437,7 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), if idx != 0 { f.write_str(", ")?; } - write!(f, "{lifetime}")?; + write!(f, "{}", lifetime.display(f.db.upcast()))?; } f.write_str("> ")?; write_target(target, f)?; @@ -461,7 +467,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { let data = db.const_data(self.id); f.write_str("const ")?; match &data.name { - Some(name) => write!(f, "{name}: ")?, + Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?, None => f.write_str("_: ")?, } data.type_ref.hir_fmt(f)?; @@ -477,7 +483,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { if data.mutable { f.write_str("mut ")?; } - write!(f, "{}: ", &data.name)?; + write!(f, "{}: ", data.name.display(f.db.upcast()))?; data.type_ref.hir_fmt(f)?; Ok(()) } @@ -493,7 +499,7 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { if data.is_auto { f.write_str("auto ")?; } - write!(f, "trait {}", data.name)?; + write!(f, "trait {}", data.name.display(f.db.upcast()))?; let def_id = GenericDefId::TraitId(self.id); write_generic_params(def_id, f)?; write_where_clause(def_id, f)?; @@ -505,7 +511,7 @@ impl HirDisplay for TraitAlias { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; let data = f.db.trait_alias_data(self.id); - write!(f, "trait {}", data.name)?; + write!(f, "trait {}", data.name.display(f.db.upcast()))?; let def_id = GenericDefId::TraitAliasId(self.id); write_generic_params(def_id, f)?; f.write_str(" = ")?; @@ -521,7 +527,7 @@ impl HirDisplay for TypeAlias { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { 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)?; + write!(f, "type {}", data.name.display(f.db.upcast()))?; let def_id = GenericDefId::TypeAliasId(self.id); write_generic_params(def_id, f)?; write_where_clause(def_id, f)?; @@ -541,8 +547,8 @@ impl HirDisplay for Module { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { // FIXME: Module doesn't have visibility saved in data. match self.name(f.db) { - Some(name) => write!(f, "mod {name}"), - None if self.is_crate_root(f.db) => match self.krate(f.db).display_name(f.db) { + Some(name) => write!(f, "mod {}", name.display(f.db.upcast())), + None if self.is_crate_root() => match self.krate(f.db).display_name(f.db) { Some(name) => write!(f, "extern crate {name}"), None => f.write_str("extern crate {unknown}"), }, @@ -558,6 +564,6 @@ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { hir_def::MacroId::MacroRulesId(_) => f.write_str("macro_rules!"), hir_def::MacroId::ProcMacroId(_) => f.write_str("proc_macro"), }?; - write!(f, " {}", self.name(f.db)) + write!(f, " {}", self.name(f.db).display(f.db.upcast())) } } diff --git a/src/tools/rust-analyzer/crates/hir/src/from_id.rs b/src/tools/rust-analyzer/crates/hir/src/from_id.rs index aaaa7abf3863..883e6a29b06a 100644 --- a/src/tools/rust-analyzer/crates/hir/src/from_id.rs +++ b/src/tools/rust-analyzer/crates/hir/src/from_id.rs @@ -4,7 +4,7 @@ //! are splitting the hir. use hir_def::{ - expr::{BindingId, LabelId}, + hir::{BindingId, LabelId}, AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, GenericParamId, ModuleDefId, VariantId, }; diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 35424feec8b2..5926d8654210 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -6,7 +6,7 @@ //! applied. So, the relation between syntax and HIR is many-to-one. //! //! HIR is the public API of the all of the compiler logic above syntax trees. -//! It is written in "OO" style. Each type is self contained (as in, it knows it's +//! It is written in "OO" style. Each type is self contained (as in, it knows its //! parents and full context). It should be "clean code". //! //! `hir_*` crates are the implementation of the compiler logic. @@ -33,24 +33,25 @@ mod display; -use std::{iter, ops::ControlFlow, sync::Arc}; +use std::{iter, ops::ControlFlow}; use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId, ProcMacroKind}; use either::Either; use hir_def::{ - adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, - expr::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat}, + data::adt::VariantData, generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, + hir::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat}, item_tree::ItemTreeNode, - lang_item::{LangItem, LangItemTarget}, - layout::{Layout, LayoutError, ReprOptions}, + lang_item::LangItemTarget, + layout::{self, ReprOptions, TargetDataLayout}, + macro_id_to_def_id, nameres::{self, diagnostics::DefDiagnostic, ModuleOrigin}, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, - AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, + AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, @@ -61,14 +62,15 @@ consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt}, diagnostics::BodyValidationDiagnostic, display::HexifiedConst, - layout::layout_of_ty, + layout::{Layout as TyLayout, RustcEnumVariantIdx, TagEncoding}, method_resolution::{self, TyFingerprint}, mir::{self, interpret_mir}, primitive::UintTy, traits::FnTrait, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, - TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause, + TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId, + WhereClause, }; use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; @@ -79,6 +81,7 @@ ast::{self, HasAttrs as _, HasDocComments, HasName}, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, T, }; +use triomphe::Arc; use crate::db::{DefDatabase, HirDatabase}; @@ -86,11 +89,13 @@ attrs::{HasAttrs, Namespace}, diagnostics::{ AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncoherentImpl, - IncorrectCase, InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, - MissingFields, MissingMatchArms, MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, - PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, - UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall, - UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut, + IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MacroExpansionParseError, + MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe, + MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField, + ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel, + UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField, + UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule, + UnresolvedProcMacro, UnusedMut, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits}, @@ -108,20 +113,18 @@ pub use { cfg::{CfgAtom, CfgExpr, CfgOptions}, hir_def::{ - adt::StructKind, - attr::{Attrs, AttrsWithOwner, Documentation}, - builtin_attr::AttributeTemplate, + attr::{builtin::AttributeTemplate, Attrs, AttrsWithOwner, Documentation}, + data::adt::StructKind, find_path::PrefixKind, import_map, - nameres::ModuleSource, + lang_item::LangItem, + nameres::{DefMap, ModuleSource}, path::{ModPath, PathKind}, type_ref::{Mutability, TypeRef}, visibility::Visibility, - // FIXME: This is here since it is input of a method in `HirWrite` - // and things outside of hir need to implement that trait. We probably - // should move whole `hir_ty::display` to this crate so we will become - // able to use `ModuleDef` or `Definition` instead of `ModuleDefId`. - ModuleDefId, + // FIXME: This is here since some queries take it as input that are used + // outside of hir. + {AdtId, ModuleDefId}, }, hir_expand::{ attrs::Attr, @@ -129,7 +132,8 @@ ExpandResult, HirFileId, InFile, MacroFile, Origin, }, hir_ty::{ - display::{HirDisplay, HirDisplayError, HirWrite}, + display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, + layout::LayoutError, mir::MirEvalError, PointerCast, Safety, }, @@ -198,7 +202,7 @@ pub fn transitive_reverse_dependencies( pub fn root_module(self, db: &dyn HirDatabase) -> Module { let def_map = db.crate_def_map(self.id); - Module { id: def_map.module_id(def_map.root()) } + Module { id: def_map.module_id(DefMap::ROOT) } } pub fn modules(self, db: &dyn HirDatabase) -> Vec { @@ -253,7 +257,8 @@ pub fn cfg(&self, db: &dyn HirDatabase) -> CfgOptions { } pub fn potential_cfg(&self, db: &dyn HirDatabase) -> CfgOptions { - db.crate_graph()[self.id].potential_cfg_options.clone() + let data = &db.crate_graph()[self.id]; + data.potential_cfg_options.clone().unwrap_or_else(|| data.cfg_options.clone()) } } @@ -326,7 +331,7 @@ pub fn canonical_path(&self, db: &dyn HirDatabase) -> Option { segments.extend(m.name(db)) } segments.reverse(); - Some(segments.into_iter().join("::")) + Some(segments.iter().map(|it| it.display(db.upcast())).join("::")) } pub fn canonical_module_path( @@ -470,12 +475,11 @@ pub fn krate(self) -> Crate { /// in the module tree of any target in `Cargo.toml`. pub fn crate_root(self, db: &dyn HirDatabase) -> Module { let def_map = db.crate_def_map(self.id.krate()); - Module { id: def_map.module_id(def_map.root()) } + Module { id: def_map.module_id(DefMap::ROOT) } } - pub fn is_crate_root(self, db: &dyn HirDatabase) -> bool { - let def_map = db.crate_def_map(self.id.krate()); - def_map.root() == self.id.local_id + pub fn is_crate_root(self) -> bool { + DefMap::ROOT == self.id.local_id } /// Iterates over all child modules. @@ -552,7 +556,11 @@ pub fn scope( /// Fills `acc` with the module's diagnostics. pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { let _p = profile::span("Module::diagnostics").detail(|| { - format!("{:?}", self.name(db).map_or("".into(), |name| name.to_string())) + format!( + "{:?}", + self.name(db) + .map_or("".into(), |name| name.display(db.upcast()).to_string()) + ) }); let def_map = self.id.def_map(db.upcast()); for diag in def_map.diagnostics() { @@ -562,6 +570,7 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { } emit_def_diagnostic(db, acc, diag); } + for decl in self.declarations(db) { match decl { ModuleDef::Module(m) => { @@ -600,9 +609,11 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { } acc.extend(decl.diagnostics(db)) } + ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m), _ => acc.extend(decl.diagnostics(db)), } } + self.legacy_macros(db).into_iter().for_each(|m| emit_macro_def_diagnostics(db, acc, m)); let inherent_impls = db.inherent_impls_in_crate(self.id.krate()); @@ -684,8 +695,31 @@ pub fn find_use_path_prefixed( } } +fn emit_macro_def_diagnostics(db: &dyn HirDatabase, acc: &mut Vec, m: Macro) { + let id = macro_id_to_def_id(db.upcast(), m.id); + if let Err(e) = db.macro_def(id) { + let Some(ast) = id.ast_id().left() else { + never!("MacroDefError for proc-macro: {:?}", e); + return; + }; + emit_def_diagnostic_( + db, + acc, + &DefDiagnosticKind::MacroDefError { ast, message: e.to_string() }, + ); + } +} + fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec, diag: &DefDiagnostic) { - match &diag.kind { + emit_def_diagnostic_(db, acc, &diag.kind) +} + +fn emit_def_diagnostic_( + db: &dyn HirDatabase, + acc: &mut Vec, + diag: &DefDiagnosticKind, +) { + match diag { DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => { let decl = declaration.to_node(db.upcast()); acc.push( @@ -725,7 +759,6 @@ fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec, diag: .into(), ); } - DefDiagnosticKind::UnresolvedProcMacro { ast, krate } => { let (node, precise_location, macro_name, kind) = precise_macro_call_location(ast, db); acc.push( @@ -733,7 +766,6 @@ fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec, diag: .into(), ); } - DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { let (node, precise_location, _, _) = precise_macro_call_location(ast, db); acc.push( @@ -746,12 +778,16 @@ fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec, diag: .into(), ); } - DefDiagnosticKind::MacroError { ast, message } => { let (node, precise_location, _, _) = precise_macro_call_location(ast, db); acc.push(MacroError { node, precise_location, message: message.clone() }.into()); } - + DefDiagnosticKind::MacroExpansionParseError { ast, errors } => { + let (node, precise_location, _, _) = precise_macro_call_location(ast, db); + acc.push( + MacroExpansionParseError { node, precise_location, errors: errors.clone() }.into(), + ); + } DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => { let node = ast.to_node(db.upcast()); // Must have a name, otherwise we wouldn't emit it. @@ -793,6 +829,17 @@ fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec, diag: None => stdx::never!("derive diagnostic on item without derive attribute"), } } + DefDiagnosticKind::MacroDefError { ast, message } => { + let node = ast.to_node(db.upcast()); + acc.push( + MacroDefError { + node: InFile::new(ast.file_id, AstPtr::new(&node)), + name: node.name().map(|it| it.syntax().text_range()), + message: message.clone(), + } + .into(), + ); + } } } @@ -800,7 +847,7 @@ fn precise_macro_call_location( ast: &MacroCallKind, db: &dyn HirDatabase, ) -> (InFile, Option, Option, MacroKind) { - // FIXME: maaybe we actually want slightly different ranges for the different macro diagnostics + // FIXME: maybe we actually want slightly different ranges for the different macro diagnostics // - e.g. the full attribute for macro errors, but only the name for name resolution match ast { MacroCallKind::FnLike { ast_id, .. } => { @@ -915,7 +962,8 @@ pub fn ty(&self, db: &dyn HirDatabase) -> Type { } pub fn layout(&self, db: &dyn HirDatabase) -> Result { - layout_of_ty(db, &self.ty(db).ty, self.parent.module(db).krate().into()) + db.layout_of_ty(self.ty(db).ty.clone(), self.parent.module(db).krate().into()) + .map(|layout| Layout(layout, db.target_data_layout(self.krate(db).into()).unwrap())) } pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef { @@ -959,6 +1007,10 @@ pub fn ty(self, db: &dyn HirDatabase) -> Type { Type::from_def(db, self.id) } + pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type { + Type::from_value_def(db, self.id) + } + pub fn repr(self, db: &dyn HirDatabase) -> Option { db.struct_data(self.id).repr } @@ -996,6 +1048,10 @@ pub fn ty(self, db: &dyn HirDatabase) -> Type { Type::from_def(db, self.id) } + pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type { + Type::from_value_def(db, self.id) + } + pub fn fields(self, db: &dyn HirDatabase) -> Vec { db.union_data(self.id) .variant_data @@ -1034,6 +1090,10 @@ pub fn variants(self, db: &dyn HirDatabase) -> Vec { db.enum_data(self.id).variants.iter().map(|(id, _)| Variant { parent: self, id }).collect() } + pub fn repr(self, db: &dyn HirDatabase) -> Option { + db.enum_data(self.id).repr + } + pub fn ty(self, db: &dyn HirDatabase) -> Type { Type::from_def(db, self.id) } @@ -1043,7 +1103,7 @@ 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() { - hir_def::layout::IntegerType::Pointer(sign) => match sign { + layout::IntegerType::Pointer(sign) => match sign { true => hir_def::builtin_type::BuiltinType::Int( hir_def::builtin_type::BuiltinInt::Isize, ), @@ -1051,29 +1111,34 @@ pub fn variant_body_ty(self, db: &dyn HirDatabase) -> Type { hir_def::builtin_type::BuiltinUint::Usize, ), }, - hir_def::layout::IntegerType::Fixed(i, sign) => match sign { + layout::IntegerType::Fixed(i, sign) => match sign { true => hir_def::builtin_type::BuiltinType::Int(match i { - hir_def::layout::Integer::I8 => hir_def::builtin_type::BuiltinInt::I8, - hir_def::layout::Integer::I16 => hir_def::builtin_type::BuiltinInt::I16, - hir_def::layout::Integer::I32 => hir_def::builtin_type::BuiltinInt::I32, - hir_def::layout::Integer::I64 => hir_def::builtin_type::BuiltinInt::I64, - hir_def::layout::Integer::I128 => hir_def::builtin_type::BuiltinInt::I128, + layout::Integer::I8 => hir_def::builtin_type::BuiltinInt::I8, + layout::Integer::I16 => hir_def::builtin_type::BuiltinInt::I16, + layout::Integer::I32 => hir_def::builtin_type::BuiltinInt::I32, + layout::Integer::I64 => hir_def::builtin_type::BuiltinInt::I64, + layout::Integer::I128 => hir_def::builtin_type::BuiltinInt::I128, }), false => hir_def::builtin_type::BuiltinType::Uint(match i { - hir_def::layout::Integer::I8 => hir_def::builtin_type::BuiltinUint::U8, - hir_def::layout::Integer::I16 => hir_def::builtin_type::BuiltinUint::U16, - hir_def::layout::Integer::I32 => hir_def::builtin_type::BuiltinUint::U32, - hir_def::layout::Integer::I64 => hir_def::builtin_type::BuiltinUint::U64, - hir_def::layout::Integer::I128 => hir_def::builtin_type::BuiltinUint::U128, + layout::Integer::I8 => hir_def::builtin_type::BuiltinUint::U8, + layout::Integer::I16 => hir_def::builtin_type::BuiltinUint::U16, + layout::Integer::I32 => hir_def::builtin_type::BuiltinUint::U32, + layout::Integer::I64 => hir_def::builtin_type::BuiltinUint::U64, + layout::Integer::I128 => hir_def::builtin_type::BuiltinUint::U128, }), }, }), ) } + /// Returns true if at least one variant of this enum is a non-unit variant. pub fn is_data_carrying(self, db: &dyn HirDatabase) -> bool { self.variants(db).iter().any(|v| !matches!(v.kind(db), StructKind::Unit)) } + + pub fn layout(self, db: &dyn HirDatabase) -> Result { + Adt::from(self).layout(db) + } } impl HasVisibility for Enum { @@ -1103,6 +1168,10 @@ pub fn parent_enum(self, _db: &dyn HirDatabase) -> Enum { self.parent } + pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type { + Type::from_value_def(db, EnumVariantId { parent: self.parent.id, local_id: self.id }) + } + pub fn name(self, db: &dyn HirDatabase) -> Name { db.enum_data(self.parent.id).variants[self.id].name.clone() } @@ -1130,6 +1199,18 @@ pub fn value(self, db: &dyn HirDatabase) -> Option { pub fn eval(self, db: &dyn HirDatabase) -> Result { db.const_eval_discriminant(self.into()) } + + pub fn layout(&self, db: &dyn HirDatabase) -> Result { + let parent_enum = self.parent_enum(db); + let parent_layout = parent_enum.layout(db)?; + Ok(match &parent_layout.0.variants { + layout::Variants::Multiple { variants, .. } => Layout( + Arc::new(variants[RustcEnumVariantIdx(self.id)].clone()), + db.target_data_layout(parent_enum.krate(db).into()).unwrap(), + ), + _ => parent_layout, + }) + } } /// Variants inherit visibility from the parent enum. @@ -1161,7 +1242,9 @@ pub fn layout(self, db: &dyn HirDatabase) -> Result { if db.generic_params(self.into()).iter().count() != 0 { return Err(LayoutError::HasPlaceholder); } - db.layout_of_adt(self.into(), Substitution::empty(Interner)) + let krate = self.krate(db).id; + db.layout_of_adt(self.into(), Substitution::empty(Interner), krate) + .map(|layout| Layout(layout, db.target_data_layout(krate).unwrap())) } /// Turns this ADT into a type. Any type parameters of the ADT will be @@ -1392,6 +1475,12 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { } .into(), ), + BodyDiagnostic::UnreachableLabel { node, name } => { + acc.push(UnreachableLabel { node: node.clone(), name: name.clone() }.into()) + } + BodyDiagnostic::UndeclaredLabel { node, name } => { + acc.push(UndeclaredLabel { node: node.clone(), name: name.clone() }.into()) + } } } @@ -1404,14 +1493,6 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { let field = source_map.field_syntax(expr); acc.push(NoSuchField { field }.into()) } - &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { - expr, - is_break, - bad_value_break, - } => { - let expr = expr_syntax(expr); - acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) - } &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { acc.push( MismatchedArgCount { call_expr: expr_syntax(call_expr), expected, found } @@ -1483,14 +1564,29 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { .into(), ) } + &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { + expr, + is_break, + bad_value_break, + } => { + let expr = expr_syntax(expr); + acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) + } + hir_ty::InferenceDiagnostic::TypedHole { expr, expected } => { + let expr = expr_syntax(*expr); + acc.push( + TypedHole { + expr, + expected: Type::new(db, DefWithBodyId::from(self), expected.clone()), + } + .into(), + ) + } } } for (pat_or_expr, mismatch) in infer.type_mismatches() { let expr_or_pat = match pat_or_expr { ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left), - // FIXME: Re-enable these once we have less false positives - ExprOrPatId::PatId(_pat) => continue, - #[allow(unreachable_patterns)] ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right), }; let expr_or_pat = match expr_or_pat { @@ -1515,7 +1611,7 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { match source_map.expr_syntax(expr) { Ok(expr) => acc.push(MissingUnsafe { expr }.into()), Err(SyntheticSyntax) => { - // FIXME: Here and eslwhere in this file, the `expr` was + // FIXME: Here and elsewhere in this file, the `expr` was // desugared, report or assert that this doesn't happen. } } @@ -1523,35 +1619,71 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { let hir_body = db.body(self.into()); - if let Ok(borrowck_result) = db.borrowck(self.into()) { - let mir_body = &borrowck_result.mir_body; - let mol = &borrowck_result.mutability_of_locals; - for (binding_id, _) in hir_body.bindings.iter() { - let need_mut = &mol[mir_body.binding_locals[binding_id]]; - let local = Local { parent: self.into(), binding_id }; - match (need_mut, local.is_mut(db)) { - (mir::MutabilityReason::Mut { .. }, true) - | (mir::MutabilityReason::Not, false) => (), - (mir::MutabilityReason::Mut { spans }, false) => { - for span in spans { - let span: InFile = match span { - mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) { - Ok(s) => s.map(|x| x.into()), - Err(_) => continue, - }, - mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) { - Ok(s) => s.map(|x| match x { - Either::Left(e) => e.into(), - Either::Right(e) => e.into(), - }), - Err(_) => continue, - }, - mir::MirSpan::Unknown => continue, - }; - acc.push(NeedMut { local, span }.into()); + if let Ok(borrowck_results) = db.borrowck(self.into()) { + for borrowck_result in borrowck_results.iter() { + let mir_body = &borrowck_result.mir_body; + for moof in &borrowck_result.moved_out_of_ref { + let span: InFile = match moof.span { + mir::MirSpan::ExprId(e) => match source_map.expr_syntax(e) { + Ok(s) => s.map(|x| x.into()), + Err(_) => continue, + }, + mir::MirSpan::PatId(p) => match source_map.pat_syntax(p) { + Ok(s) => s.map(|x| match x { + Either::Left(e) => e.into(), + Either::Right(e) => e.into(), + }), + Err(_) => continue, + }, + mir::MirSpan::Unknown => continue, + }; + acc.push( + MovedOutOfRef { ty: Type::new_for_crate(krate, moof.ty.clone()), span } + .into(), + ) + } + let mol = &borrowck_result.mutability_of_locals; + for (binding_id, binding_data) in hir_body.bindings.iter() { + if binding_data.problems.is_some() { + // We should report specific diagnostics for these problems, not `need-mut` and `unused-mut`. + continue; + } + let Some(&local) = mir_body.binding_locals.get(binding_id) else { + continue; + }; + let need_mut = &mol[local]; + let local = Local { parent: self.into(), binding_id }; + match (need_mut, local.is_mut(db)) { + (mir::MutabilityReason::Mut { .. }, true) + | (mir::MutabilityReason::Not, false) => (), + (mir::MutabilityReason::Mut { spans }, false) => { + for span in spans { + let span: InFile = match span { + mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) { + Ok(s) => s.map(|x| x.into()), + Err(_) => continue, + }, + mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) { + Ok(s) => s.map(|x| match x { + Either::Left(e) => e.into(), + Either::Right(e) => e.into(), + }), + Err(_) => continue, + }, + mir::MirSpan::Unknown => continue, + }; + acc.push(NeedMut { local, span }.into()); + } + } + (mir::MutabilityReason::Not, true) => { + if !infer.mutated_bindings_in_closure.contains(&binding_id) { + let should_ignore = matches!(body[binding_id].name.as_str(), Some(x) if x.starts_with("_")); + if !should_ignore { + acc.push(UnusedMut { local }.into()) + } + } } } - (mir::MutabilityReason::Not, true) => acc.push(UnusedMut { local }.into()), } } } @@ -1686,6 +1818,10 @@ pub fn name(self, db: &dyn HirDatabase) -> Name { db.function_data(self.id).name.clone() } + pub fn ty(self, db: &dyn HirDatabase) -> Type { + Type::from_value_def(db, self.id) + } + /// Get this function's return type pub fn ret_type(self, db: &dyn HirDatabase) -> Type { let resolver = self.id.resolver(db.upcast()); @@ -1797,12 +1933,41 @@ pub fn as_proc_macro(self, db: &dyn HirDatabase) -> Option { def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() }) } - pub fn eval(self, db: &dyn HirDatabase) -> Result<(), MirEvalError> { - let body = db - .mir_body(self.id.into()) - .map_err(|e| MirEvalError::MirLowerError(self.id.into(), e))?; - interpret_mir(db, &body, false)?; - Ok(()) + pub fn eval( + self, + db: &dyn HirDatabase, + span_formatter: impl Fn(FileId, TextRange) -> String, + ) -> String { + let body = match db.monomorphized_mir_body( + self.id.into(), + Substitution::empty(Interner), + db.trait_environment(self.id.into()), + ) { + Ok(body) => body, + Err(e) => { + let mut r = String::new(); + _ = e.pretty_print(&mut r, db, &span_formatter); + return r; + } + }; + let (result, stdout, stderr) = interpret_mir(db, &body, false); + let mut text = match result { + Ok(_) => "pass".to_string(), + Err(e) => { + let mut r = String::new(); + _ = e.pretty_print(&mut r, db, &span_formatter); + r + } + }; + if !stdout.is_empty() { + text += "\n--------- stdout ---------\n"; + text += &stdout; + } + if !stderr.is_empty() { + text += "\n--------- stderr ---------\n"; + text += &stderr; + } + text } } @@ -1837,7 +2002,7 @@ pub fn ty(&self) -> &Type { } pub fn name(&self, db: &dyn HirDatabase) -> Option { - db.function_data(self.func.id).params[self.idx].0.clone() + Some(self.as_local(db)?.name(db)) } pub fn as_local(&self, db: &dyn HirDatabase) -> Option { @@ -1878,7 +2043,7 @@ pub fn access(self, db: &dyn HirDatabase) -> Access { func_data .params .first() - .map(|(_, param)| match &**param { + .map(|param| match &**param { TypeRef::Reference(.., mutability) => match mutability { hir_def::type_ref::Mutability::Shared => Access::Shared, hir_def::type_ref::Mutability::Mut => Access::Exclusive, @@ -1939,24 +2104,12 @@ pub fn value(self, db: &dyn HirDatabase) -> Option { } pub fn ty(self, db: &dyn HirDatabase) -> Type { - let data = db.const_data(self.id); - let resolver = self.id.resolver(db.upcast()); - let ctx = hir_ty::TyLoweringContext::new(db, &resolver); - let ty = ctx.lower_ty(&data.type_ref); - Type::new_with_resolver_inner(db, &resolver, ty) + Type::from_value_def(db, self.id) } pub fn render_eval(self, db: &dyn HirDatabase) -> Result { - let c = db.const_eval(self.id)?; + let c = db.const_eval(self.id.into(), Substitution::empty(Interner))?; let r = format!("{}", HexifiedConst(c).display(db)); - // We want to see things like `` and `` as they are probably bug in our - // implementation, but there is no need to show things like `` or `` to - // the user. - if r.contains("not-supported>") { - return Err(ConstEvalError::MirEvalError(MirEvalError::NotSupported( - "rendering complex constants".to_string(), - ))); - } return Ok(r); } } @@ -1990,11 +2143,7 @@ pub fn value(self, db: &dyn HirDatabase) -> Option { } pub fn ty(self, db: &dyn HirDatabase) -> Type { - let data = db.static_data(self.id); - let resolver = self.id.resolver(db.upcast()); - let ctx = hir_ty::TyLoweringContext::new(db, &resolver); - let ty = ctx.lower_ty(&data.type_ref); - Type::new_with_resolver_inner(db, &resolver, ty) + Type::from_value_def(db, self.id) } } @@ -2545,8 +2694,12 @@ pub fn original_file(&self, db: &dyn HirDatabase) -> FileId { self.source.file_id.original_file(db.upcast()) } - pub fn name(&self) -> Option { - self.source.value.name() + pub fn file(&self) -> HirFileId { + self.source.file_id + } + + pub fn name(&self) -> Option> { + self.source.as_ref().map(|it| it.name()).transpose() } pub fn syntax(&self) -> &SyntaxNode { @@ -2689,9 +2842,7 @@ pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option< } fn builtin(name: &str) -> Option { - hir_def::builtin_attr::INERT_ATTRIBUTES - .iter() - .position(|tool| tool.name == name) + hir_def::attr::builtin::find_builtin_attr_idx(name) .map(|idx| BuiltinAttr { krate: None, idx: idx as u32 }) } @@ -2699,14 +2850,14 @@ pub fn name(&self, db: &dyn HirDatabase) -> SmolStr { // FIXME: Return a `Name` here match self.krate { Some(krate) => db.crate_def_map(krate).registered_attrs()[self.idx as usize].clone(), - None => SmolStr::new(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx as usize].name), + None => SmolStr::new(hir_def::attr::builtin::INERT_ATTRIBUTES[self.idx as usize].name), } } pub fn template(&self, _: &dyn HirDatabase) -> Option { match self.krate { Some(_) => None, - None => Some(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx as usize].template), + None => Some(hir_def::attr::builtin::INERT_ATTRIBUTES[self.idx as usize].template), } } } @@ -2729,7 +2880,7 @@ pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option< } fn builtin(name: &str) -> Option { - hir_def::builtin_attr::TOOL_MODULES + hir_def::attr::builtin::TOOL_MODULES .iter() .position(|&tool| tool == name) .map(|idx| ToolModule { krate: None, idx: idx as u32 }) @@ -2739,7 +2890,7 @@ pub fn name(&self, db: &dyn HirDatabase) -> SmolStr { // FIXME: Return a `Name` here match self.krate { Some(krate) => db.crate_def_map(krate).registered_tools()[self.idx as usize].clone(), - None => SmolStr::new(hir_def::builtin_attr::TOOL_MODULES[self.idx as usize]), + None => SmolStr::new(hir_def::attr::builtin::TOOL_MODULES[self.idx as usize]), } } } @@ -3117,6 +3268,103 @@ pub fn get_type_argument(&self, idx: usize) -> Option { } } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Closure { + id: ClosureId, + subst: Substitution, +} + +impl From for ClosureId { + fn from(value: Closure) -> Self { + value.id + } +} + +impl Closure { + fn as_ty(self) -> Ty { + TyKind::Closure(self.id, self.subst).intern(Interner) + } + + pub fn display_with_id(&self, db: &dyn HirDatabase) -> String { + self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string() + } + + pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String { + self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string() + } + + pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec { + let owner = db.lookup_intern_closure((self.id).into()).0; + let infer = &db.infer(owner); + let info = infer.closure_info(&self.id); + info.0 + .iter() + .cloned() + .map(|capture| ClosureCapture { owner, closure: self.id, capture }) + .collect() + } + + pub fn capture_types(&self, db: &dyn HirDatabase) -> Vec { + let owner = db.lookup_intern_closure((self.id).into()).0; + let infer = &db.infer(owner); + let (captures, _) = infer.closure_info(&self.id); + captures + .iter() + .cloned() + .map(|capture| Type { + env: db.trait_environment_for_body(owner), + ty: capture.ty(&self.subst), + }) + .collect() + } + + pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait { + let owner = db.lookup_intern_closure((self.id).into()).0; + let infer = &db.infer(owner); + let info = infer.closure_info(&self.id); + info.1 + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClosureCapture { + owner: DefWithBodyId, + closure: ClosureId, + capture: hir_ty::CapturedItem, +} + +impl ClosureCapture { + pub fn local(&self) -> Local { + Local { parent: self.owner, binding_id: self.capture.local() } + } + + pub fn kind(&self) -> CaptureKind { + match self.capture.kind() { + hir_ty::CaptureKind::ByRef( + hir_ty::mir::BorrowKind::Shallow | hir_ty::mir::BorrowKind::Shared, + ) => CaptureKind::SharedRef, + hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Unique) => { + CaptureKind::UniqueSharedRef + } + hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Mut { .. }) => { + CaptureKind::MutableRef + } + hir_ty::CaptureKind::ByValue => CaptureKind::Move, + } + } + + pub fn display_place(&self, db: &dyn HirDatabase) -> String { + self.capture.display_place(self.owner, db) + } +} + +pub enum CaptureKind { + SharedRef, + UniqueSharedRef, + MutableRef, + Move, +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct Type { env: Arc, @@ -3164,24 +3412,33 @@ fn new(db: &dyn HirDatabase, lexical_env: impl HasResolver, ty: Ty) -> Type { Type { env: environment, ty } } - fn from_def(db: &dyn HirDatabase, def: impl HasResolver + Into) -> Type { - 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, + fn from_def(db: &dyn HirDatabase, def: impl Into + HasResolver) -> Type { + let ty = db.ty(def.into()); + let substs = TyBuilder::unknown_subst( + db, + match def.into() { + TyDefId::AdtId(it) => GenericDefId::AdtId(it), + TyDefId::TypeAliasId(it) => GenericDefId::TypeAliasId(it), + TyDefId::BuiltinType(_) => return Type::new(db, def, ty.skip_binders().clone()), }, - _ => None, - }; - let ty = TyBuilder::def_ty(db, ty_def, parent_subst).fill_with_unknown().build(); - Type::new(db, def, ty) + ); + Type::new(db, def, ty.substitute(Interner, &substs)) + } + + fn from_value_def(db: &dyn HirDatabase, def: impl Into + HasResolver) -> Type { + let ty = db.value_ty(def.into()); + let substs = TyBuilder::unknown_subst( + db, + match def.into() { + ValueTyDefId::ConstId(it) => GenericDefId::ConstId(it), + ValueTyDefId::FunctionId(it) => GenericDefId::FunctionId(it), + ValueTyDefId::StructId(it) => GenericDefId::AdtId(AdtId::StructId(it)), + ValueTyDefId::UnionId(it) => GenericDefId::AdtId(AdtId::UnionId(it)), + ValueTyDefId::EnumVariantId(it) => GenericDefId::EnumVariantId(it), + ValueTyDefId::StaticId(_) => return Type::new(db, def, ty.skip_binders().clone()), + }, + ); + Type::new(db, def, ty.substitute(Interner, &substs)) } pub fn new_slice(ty: Type) -> Type { @@ -3331,7 +3588,7 @@ pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> binders: CanonicalVarKinds::empty(Interner), }; - db.trait_solve(self.env.krate, goal).is_some() + db.trait_solve(self.env.krate, self.env.block, goal).is_some() } pub fn normalize_trait_assoc_type( @@ -3378,7 +3635,12 @@ pub fn is_copy(&self, db: &dyn HirDatabase) -> bool { } pub fn as_callable(&self, db: &dyn HirDatabase) -> Option { + let mut the_ty = &self.ty; let callee = match self.ty.kind(Interner) { + TyKind::Ref(_, _, ty) if ty.as_closure().is_some() => { + the_ty = ty; + Callee::Closure(ty.as_closure().unwrap()) + } TyKind::Closure(id, _) => Callee::Closure(*id), TyKind::Function(_) => Callee::FnPtr, TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?), @@ -3393,7 +3655,7 @@ pub fn as_callable(&self, db: &dyn HirDatabase) -> Option { } }; - let sig = self.ty.callable_sig(db)?; + let sig = the_ty.callable_sig(db)?; Some(Callable { ty: self.clone(), sig, callee, is_bound_method: false }) } @@ -3401,6 +3663,13 @@ pub fn is_closure(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::Closure { .. }) } + pub fn as_closure(&self) -> Option { + match self.ty.kind(Interner) { + TyKind::Closure(id, subst) => Some(Closure { id: *id, subst: subst.clone() }), + _ => None, + } + } + pub fn is_fn(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. }) } @@ -3502,9 +3771,9 @@ pub fn tuple_fields(&self, _db: &dyn HirDatabase) -> Vec { } } - pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> { + pub fn as_array(&self, db: &dyn HirDatabase) -> Option<(Type, usize)> { if let TyKind::Array(ty, len) = &self.ty.kind(Interner) { - try_const_usize(len).map(|x| (self.derived(ty.clone()), x as usize)) + try_const_usize(db, len).map(|x| (self.derived(ty.clone()), x as usize)) } else { None } @@ -3517,8 +3786,7 @@ pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator(&'a self, db: &'a dyn HirDatabase) -> impl Iterator + 'a { // There should be no inference vars in types passed here let canonical = hir_ty::replace_errors_with_variables(&self.ty); - let environment = self.env.clone(); - autoderef(db, environment, canonical).map(|canonical| canonical.value) + autoderef(db, self.env.clone(), canonical).map(|canonical| canonical.value) } // This would be nicer if it just returned an iterator, but that runs into @@ -3636,7 +3904,7 @@ pub fn generic_parameters<'a>( self.as_adt() .and_then(|a| a.lifetime(db).and_then(|lt| Some((<.name).to_smol_str()))) .into_iter() - // add the type and const paramaters + // add the type and const parameters .chain(self.type_and_const_arguments(db)) } @@ -3955,6 +4223,11 @@ pub fn generic_params(&self, db: &dyn HirDatabase) -> FxHashSet { .map(|id| TypeOrConstParam { id }.split(db).either_into()) .collect() } + + pub fn layout(&self, db: &dyn HirDatabase) -> Result { + db.layout_of_ty(self.ty.clone(), self.env.krate) + .map(|layout| Layout(layout, db.target_data_layout(self.env.krate).unwrap())) + } } // FIXME: Document this @@ -4064,6 +4337,48 @@ fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option, Arc); + +impl Layout { + pub fn size(&self) -> u64 { + self.0.size.bytes() + } + + pub fn align(&self) -> u64 { + self.0.align.abi.bytes() + } + + pub fn niches(&self) -> Option { + Some(self.0.largest_niche?.available(&*self.1)) + } + + pub fn field_offset(&self, idx: usize) -> Option { + match self.0.fields { + layout::FieldsShape::Primitive => None, + layout::FieldsShape::Union(_) => Some(0), + layout::FieldsShape::Array { stride, count } => { + let i = u64::try_from(idx).ok()?; + (i < count).then_some((stride * i).bytes()) + } + layout::FieldsShape::Arbitrary { ref offsets, .. } => Some(offsets.get(idx)?.bytes()), + } + } + + pub fn enum_tag_size(&self) -> Option { + let tag_size = + if let layout::Variants::Multiple { tag, tag_encoding, .. } = &self.0.variants { + match tag_encoding { + TagEncoding::Direct => tag.size(&*self.1).bytes_usize(), + TagEncoding::Niche { .. } => 0, + } + } else { + return None; + }; + Some(tag_size) + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum BindingMode { Move, @@ -4215,6 +4530,12 @@ fn krate(&self, db: &dyn HirDatabase) -> Crate { } } +impl HasCrate for Enum { + fn krate(&self, db: &dyn HirDatabase) -> Crate { + self.module(db).krate() + } +} + impl HasCrate for Field { fn krate(&self, db: &dyn HirDatabase) -> Crate { self.parent_def(db).module(db).krate() @@ -4286,3 +4607,90 @@ fn krate(&self, _: &dyn HirDatabase) -> Crate { Module::krate(*self) } } + +pub trait HasContainer { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer; +} + +impl HasContainer for Module { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + // FIXME: handle block expressions as modules (their parent is in a different DefMap) + let def_map = self.id.def_map(db.upcast()); + match def_map[self.id.local_id].parent { + Some(parent_id) => ItemContainer::Module(Module { id: def_map.module_id(parent_id) }), + None => ItemContainer::Crate(def_map.krate()), + } + } +} + +impl HasContainer for Function { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + container_id_to_hir(self.id.lookup(db.upcast()).container) + } +} + +impl HasContainer for Struct { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + ItemContainer::Module(Module { id: self.id.lookup(db.upcast()).container }) + } +} + +impl HasContainer for Union { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + ItemContainer::Module(Module { id: self.id.lookup(db.upcast()).container }) + } +} + +impl HasContainer for Enum { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + ItemContainer::Module(Module { id: self.id.lookup(db.upcast()).container }) + } +} + +impl HasContainer for TypeAlias { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + container_id_to_hir(self.id.lookup(db.upcast()).container) + } +} + +impl HasContainer for Const { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + container_id_to_hir(self.id.lookup(db.upcast()).container) + } +} + +impl HasContainer for Static { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + container_id_to_hir(self.id.lookup(db.upcast()).container) + } +} + +impl HasContainer for Trait { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + ItemContainer::Module(Module { id: self.id.lookup(db.upcast()).container }) + } +} + +impl HasContainer for TraitAlias { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + ItemContainer::Module(Module { id: self.id.lookup(db.upcast()).container }) + } +} + +fn container_id_to_hir(c: ItemContainerId) -> ItemContainer { + match c { + ItemContainerId::ExternBlockId(_id) => ItemContainer::ExternBlock(), + ItemContainerId::ModuleId(id) => ItemContainer::Module(Module { id }), + ItemContainerId::ImplId(id) => ItemContainer::Impl(Impl { id }), + ItemContainerId::TraitId(id) => ItemContainer::Trait(Trait { id }), + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ItemContainer { + Trait(Trait), + Impl(Impl), + Module(Module), + ExternBlock(), + Crate(CrateId), +} diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 407ba6f65844..2d2b00b147e5 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -7,9 +7,10 @@ use base_db::{FileId, FileRange}; use either::Either; use hir_def::{ - body, - expr::Expr, + hir::Expr, + lower::LowerCtx, macro_id_to_def_id, + nameres::MacroSubNs, resolver::{self, HasResolver, Resolver, TypeNs}, type_ref::Mutability, AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId, @@ -140,7 +141,7 @@ pub fn parse(&self, file_id: FileId) -> ast::SourceFile { self.imp.parse(file_id) } - pub fn parse_or_expand(&self, file_id: HirFileId) -> Option { + pub fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode { self.imp.parse_or_expand(file_id) } @@ -350,6 +351,13 @@ pub fn type_of_pat(&self, pat: &ast::Pat) -> Option { self.imp.type_of_pat(pat) } + /// It also includes the changes that binding mode makes in the type. For example in + /// `let ref x @ Some(_) = None` the result of `type_of_pat` is `Option` but the result + /// of this function is `&mut Option` + pub fn type_of_binding_in_pat(&self, pat: &ast::IdentPat) -> Option { + self.imp.type_of_binding_in_pat(pat) + } + pub fn type_of_self(&self, param: &ast::SelfParam) -> Option { self.imp.type_of_self(param) } @@ -518,23 +526,23 @@ fn parse(&self, file_id: FileId) -> ast::SourceFile { tree } - fn parse_or_expand(&self, file_id: HirFileId) -> Option { - let node = self.db.parse_or_expand(file_id)?; + fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode { + let node = self.db.parse_or_expand(file_id); self.cache(node.clone(), file_id); - Some(node) + node } fn expand(&self, macro_call: &ast::MacroCall) -> Option { let sa = self.analyze_no_infer(macro_call.syntax())?; let file_id = sa.expand(self.db, InFile::new(sa.file_id, macro_call))?; - let node = self.parse_or_expand(file_id)?; + let node = self.parse_or_expand(file_id); Some(node) } fn expand_attr_macro(&self, item: &ast::Item) -> Option { let src = self.wrap_node_infile(item.clone()); let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?; - self.parse_or_expand(macro_call_id.as_file()) + Some(self.parse_or_expand(macro_call_id.as_file())) } fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option { @@ -543,7 +551,7 @@ fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option Option>> { @@ -566,7 +574,7 @@ fn expand_derive_macro(&self, attr: &ast::Attr) -> Option> { .into_iter() .flat_map(|call| { let file_id = call?.as_file(); - let node = self.db.parse_or_expand(file_id)?; + let node = self.db.parse_or_expand(file_id); self.cache(node.clone(), file_id); Some(node) }) @@ -609,7 +617,7 @@ fn speculative_expand( let krate = resolver.krate(); let macro_call_id = macro_call.as_call_id(self.db.upcast(), krate, |path| { resolver - .resolve_path_as_macro(self.db.upcast(), &path) + .resolve_path_as_macro(self.db.upcast(), &path, Some(MacroSubNs::Bang)) .map(|it| macro_id_to_def_id(self.db.upcast(), it)) })?; hir_expand::db::expand_speculative( @@ -990,7 +998,7 @@ fn original_syntax_node(&self, node: &SyntaxNode) -> Option { } fn diagnostics_display_range(&self, src: InFile) -> FileRange { - let root = self.parse_or_expand(src.file_id).unwrap(); + let root = self.parse_or_expand(src.file_id); let node = src.map(|it| it.to_node(&root)); node.as_ref().original_file_range(self.db.upcast()) } @@ -1065,7 +1073,7 @@ fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option for &F + where + F: ~const Fn, + { + extern "rust-call" fn call(&self, args: A) -> F::Output { + (**self).call(args) + } + } + + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_fn_trait_ref_impls", issue = "101803")] + impl const FnMut for &F + where + F: ~const Fn, + { + extern "rust-call" fn call_mut(&mut self, args: A) -> F::Output { + (**self).call(args) + } + } + + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_fn_trait_ref_impls", issue = "101803")] + impl const FnOnce for &F + where + F: ~const Fn, + { + type Output = F::Output; + + extern "rust-call" fn call_once(self, args: A) -> F::Output { + (*self).call(args) + } + } + + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_fn_trait_ref_impls", issue = "101803")] + impl const FnMut for &mut F + where + F: ~const FnMut, + { + extern "rust-call" fn call_mut(&mut self, args: A) -> F::Output { + (*self).call_mut(args) + } + } + + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_fn_trait_ref_impls", issue = "101803")] + impl const FnOnce for &mut F + where + F: ~const FnMut, + { + type Output = F::Output; + extern "rust-call" fn call_once(self, args: A) -> F::Output { + (*self).call_mut(args) + } + } + } } pub use self::function::{Fn, FnMut, FnOnce}; // endregion:fn // region:try mod try_ { + use super::super::convert::Infallible; + pub enum ControlFlow { + #[lang = "Continue"] Continue(C), + #[lang = "Break"] Break(B), } - pub trait FromResidual { + pub trait FromResidual::Residual> { #[lang = "from_residual"] fn from_residual(residual: R) -> Self; } @@ -400,14 +637,80 @@ pub trait Try: FromResidual { impl Try for ControlFlow { type Output = C; - type Residual = ControlFlow; - fn from_output(output: Self::Output) -> Self {} - fn branch(self) -> ControlFlow {} + type Residual = ControlFlow; + fn from_output(output: Self::Output) -> Self { + ControlFlow::Continue(output) + } + fn branch(self) -> ControlFlow { + match self { + ControlFlow::Continue(x) => ControlFlow::Continue(x), + ControlFlow::Break(x) => ControlFlow::Break(ControlFlow::Break(x)), + } + } } impl FromResidual for ControlFlow { - fn from_residual(residual: ControlFlow) -> Self {} + fn from_residual(residual: ControlFlow) -> Self { + match residual { + ControlFlow::Break(b) => ControlFlow::Break(b), + ControlFlow::Continue(_) => loop {}, + } + } } + // region:option + impl Try for Option { + type Output = T; + type Residual = Option; + fn from_output(output: Self::Output) -> Self { + Some(output) + } + fn branch(self) -> ControlFlow { + match self { + Some(x) => ControlFlow::Continue(x), + None => ControlFlow::Break(None), + } + } + } + + impl FromResidual for Option { + fn from_residual(x: Option) -> Self { + match x { + None => None, + Some(_) => loop {}, + } + } + } + // endregion:option + // region:result + // region:from + use super::super::convert::From; + + impl Try for Result { + type Output = T; + type Residual = Result; + + fn from_output(output: Self::Output) -> Self { + Ok(output) + } + + fn branch(self) -> ControlFlow { + match self { + Ok(v) => ControlFlow::Continue(v), + Err(e) => ControlFlow::Break(Err(e)), + } + } + } + + impl> FromResidual> for Result { + fn from_residual(residual: Result) -> Self { + match residual { + Err(e) => Err(From::from(e)), + Ok(_) => loop {}, + } + } + } + // endregion:from + // endregion:result } pub use self::try_::{ControlFlow, FromResidual, Try}; // endregion:try @@ -424,6 +727,19 @@ pub trait Add { pub trait AddAssign { fn add_assign(&mut self, rhs: Rhs); } + + // region:builtin_impls + macro_rules! add_impl { + ($($t:ty)*) => ($( + impl const Add for $t { + type Output = $t; + fn add(self, other: $t) -> $t { self + other } + } + )*) + } + + add_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } + // endregion:builtin_impls // endregion:add // region:generator @@ -499,12 +815,114 @@ pub mod fmt { pub struct Error; pub type Result = Result<(), Error>; pub struct Formatter<'a>; + pub struct DebugTuple; + pub struct DebugStruct; + impl Formatter<'_> { + pub fn debug_tuple(&mut self, name: &str) -> DebugTuple { + DebugTuple + } + + pub fn debug_struct(&mut self, name: &str) -> DebugStruct { + DebugStruct + } + } + + impl DebugTuple { + pub fn field(&mut self, value: &dyn Debug) -> &mut Self { + self + } + + pub fn finish(&mut self) -> Result { + Ok(()) + } + } + + impl DebugStruct { + pub fn field(&mut self, name: &str, value: &dyn Debug) -> &mut Self { + self + } + + pub fn finish(&mut self) -> Result { + Ok(()) + } + } + pub trait Debug { fn fmt(&self, f: &mut Formatter<'_>) -> Result; } pub trait Display { fn fmt(&self, f: &mut Formatter<'_>) -> Result; } + + extern "C" { + type Opaque; + } + + #[lang = "format_argument"] + pub struct ArgumentV1<'a> { + value: &'a Opaque, + formatter: fn(&Opaque, &mut Formatter<'_>) -> Result, + } + + impl<'a> ArgumentV1<'a> { + pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> ArgumentV1<'b> { + use crate::mem::transmute; + unsafe { ArgumentV1 { formatter: transmute(f), value: transmute(x) } } + } + } + + #[lang = "format_arguments"] + pub struct Arguments<'a> { + pieces: &'a [&'static str], + args: &'a [ArgumentV1<'a>], + } + + impl<'a> Arguments<'a> { + pub const fn new_v1( + pieces: &'a [&'static str], + args: &'a [ArgumentV1<'a>], + ) -> Arguments<'a> { + Arguments { pieces, args } + } + } + + // region:derive + #[rustc_builtin_macro] + pub macro Debug($item:item) {} + // endregion:derive + + // region:builtin_impls + macro_rules! impl_debug { + ($($t:ty)*) => { + $( + impl const Debug for $t { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + Ok(()) + } + } + )* + } + } + + impl_debug! { + usize u8 u16 u32 u64 u128 + isize i8 i16 i32 i64 i128 + f32 f64 + bool char + } + + impl Debug for [T] { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + Ok(()) + } + } + + impl Debug for &T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + (&**self).fmt(f) + } + } + // endregion:builtin_impls } // endregion:fmt @@ -537,12 +955,30 @@ pub const fn unwrap(self) -> T { } } + pub const fn as_ref(&self) -> Option<&T> { + match self { + Some(x) => Some(x), + None => None, + } + } + pub fn and(self, optb: Option) -> Option { loop {} } pub fn unwrap_or(self, default: T) -> T { - loop {} + match self { + Some(val) => val, + None => default, + } } + // region:result + pub const fn ok_or(self, err: E) -> Result { + match self { + Some(v) => Ok(v), + None => Err(err), + } + } + // endregion:result // region:fn pub fn and_then(self, f: F) -> Option where @@ -713,8 +1149,6 @@ fn next(&mut self) -> Option { mod traits { mod iterator { - use super::super::Take; - pub trait Iterator { type Item; #[lang = "next"] @@ -764,12 +1198,19 @@ fn into_iter(self) -> I { self } } - pub struct IntoIter([T; N]); + struct IndexRange { + start: usize, + end: usize, + } + pub struct IntoIter { + data: [T; N], + range: IndexRange, + } impl IntoIterator for [T; N] { type Item = T; type IntoIter = IntoIter; fn into_iter(self) -> I { - IntoIter(self) + IntoIter { data: self, range: IndexRange { start: 0, end: loop {} } } } } impl Iterator for IntoIter { @@ -785,16 +1226,64 @@ fn next(&mut self) -> Option { } // endregion:iterator -// region:derive +// region:panic +mod panic { + pub macro panic_2021 { + ($($t:tt)+) => ( + $crate::panicking::panic_fmt($crate::const_format_args!($($t)+)) + ), + } +} + +mod panicking { + #[lang = "panic_fmt"] + pub const fn panic_fmt(fmt: crate::fmt::Arguments<'_>) -> ! { + loop {} + } +} +// endregion:panic + mod macros { + // region:panic + #[macro_export] + #[rustc_builtin_macro(std_panic)] + macro_rules! panic { + ($($arg:tt)*) => { + /* compiler built-in */ + }; + } + + pub(crate) use panic; + // endregion:panic + + // region:fmt + #[macro_export] + #[rustc_builtin_macro] + macro_rules! const_format_args { + ($fmt:expr) => {{ /* compiler built-in */ }}; + ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; + } + + pub(crate) use const_format_args; + // endregion:fmt + + // region:derive pub(crate) mod builtin { #[rustc_builtin_macro] pub macro derive($item:item) { /* compiler built-in */ } } + // endregion:derive + + // region:include + #[rustc_builtin_macro] + #[macro_export] + macro_rules! include { + ($file:expr $(,)?) => {{ /* compiler built-in */ }}; + } + // endregion:include } -// endregion:derive // region:non_zero pub mod num { @@ -848,6 +1337,7 @@ pub mod v1 { ops::Drop, // :drop ops::{Fn, FnMut, FnOnce}, // :fn option::Option::{self, None, Some}, // :option + panic, // :panic result::Result::{self, Err, Ok}, // :result }; } diff --git a/src/tools/rust-analyzer/crates/text-edit/Cargo.toml b/src/tools/rust-analyzer/crates/text-edit/Cargo.toml index 337cd234739c..76d0ca5ccb60 100644 --- a/src/tools/rust-analyzer/crates/text-edit/Cargo.toml +++ b/src/tools/rust-analyzer/crates/text-edit/Cargo.toml @@ -13,4 +13,4 @@ doctest = false [dependencies] itertools = "0.10.5" -text-size = "1.1.0" +text-size.workspace = true diff --git a/src/tools/rust-analyzer/crates/text-edit/src/lib.rs b/src/tools/rust-analyzer/crates/text-edit/src/lib.rs index 9bb4271b65f7..4705d18187af 100644 --- a/src/tools/rust-analyzer/crates/text-edit/src/lib.rs +++ b/src/tools/rust-analyzer/crates/text-edit/src/lib.rs @@ -176,6 +176,7 @@ pub fn insert(&mut self, offset: TextSize, text: String) { pub fn finish(self) -> TextEdit { let mut indels = self.indels; assert_disjoint_or_equal(&mut indels); + indels = coalesce_indels(indels); TextEdit { indels } } pub fn invalidates_offset(&self, offset: TextSize) -> bool { @@ -205,6 +206,21 @@ fn check_disjoint<'a, I>(indels: &mut I) -> bool indels.clone().zip(indels.skip(1)).all(|(l, r)| l.delete.end() <= r.delete.start() || l == r) } +fn coalesce_indels(indels: Vec) -> Vec { + indels + .into_iter() + .coalesce(|mut a, b| { + if a.delete.end() == b.delete.start() { + a.insert.push_str(&b.insert); + a.delete = TextRange::new(a.delete.start(), b.delete.end()); + Ok(a) + } else { + Err((a, b)) + } + }) + .collect_vec() +} + #[cfg(test)] mod tests { use super::{TextEdit, TextEditBuilder, TextRange}; @@ -261,4 +277,40 @@ fn test_union_panics() { let edit2 = TextEdit::delete(range(9, 13)); assert!(edit1.union(edit2).is_err()); } + + #[test] + fn test_coalesce_disjoint() { + let mut builder = TextEditBuilder::default(); + builder.replace(range(1, 3), "aa".into()); + builder.replace(range(5, 7), "bb".into()); + let edit = builder.finish(); + + assert_eq!(edit.indels.len(), 2); + } + + #[test] + fn test_coalesce_adjacent() { + let mut builder = TextEditBuilder::default(); + builder.replace(range(1, 3), "aa".into()); + builder.replace(range(3, 5), "bb".into()); + + let edit = builder.finish(); + assert_eq!(edit.indels.len(), 1); + assert_eq!(edit.indels[0].insert, "aabb"); + assert_eq!(edit.indels[0].delete, range(1, 5)); + } + + #[test] + fn test_coalesce_adjacent_series() { + let mut builder = TextEditBuilder::default(); + builder.replace(range(1, 3), "au".into()); + builder.replace(range(3, 5), "www".into()); + builder.replace(range(5, 8), "".into()); + builder.replace(range(8, 9), "ub".into()); + + let edit = builder.finish(); + assert_eq!(edit.indels.len(), 1); + assert_eq!(edit.indels[0].insert, "auwwwub"); + assert_eq!(edit.indels[0].delete, range(1, 9)); + } } diff --git a/src/tools/rust-analyzer/crates/tt/Cargo.toml b/src/tools/rust-analyzer/crates/tt/Cargo.toml index b8469383183b..a28ee5f1ca2b 100644 --- a/src/tools/rust-analyzer/crates/tt/Cargo.toml +++ b/src/tools/rust-analyzer/crates/tt/Cargo.toml @@ -12,6 +12,6 @@ rust-version.workspace = true doctest = false [dependencies] -smol_str = "0.1.23" +smol_str.workspace = true stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index b7dbc82e1d66..c2ebf03746a8 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -153,6 +153,12 @@ pub struct Ident { pub span: Span, } +impl Ident { + pub fn new(text: impl Into, span: S) -> Self { + Ident { text: text.into(), span } + } +} + fn print_debug_subtree( f: &mut fmt::Formatter<'_>, subtree: &Subtree, diff --git a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml index e06b98d8118b..5d61a227284e 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml +++ b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml @@ -13,10 +13,10 @@ doctest = false [dependencies] tracing = "0.1.35" -jod-thread = "0.1.2" walkdir = "2.3.2" crossbeam-channel = "0.5.5" notify = "5.0" +stdx.workspace = true vfs.workspace = true paths.workspace = true diff --git a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs index c95304e55ac1..abfc51dfec66 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs @@ -21,7 +21,7 @@ pub struct NotifyHandle { // Relative order of fields below is significant. sender: Sender, - _thread: jod_thread::JoinHandle, + _thread: stdx::thread::JoinHandle, } #[derive(Debug)] @@ -34,7 +34,7 @@ impl loader::Handle for NotifyHandle { fn spawn(sender: loader::Sender) -> NotifyHandle { let actor = NotifyActor::new(sender); let (sender, receiver) = unbounded::(); - let thread = jod_thread::Builder::new() + let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) .name("VfsLoader".to_owned()) .spawn(move || actor.run(receiver)) .expect("failed to spawn thread"); diff --git a/src/tools/rust-analyzer/crates/vfs/Cargo.toml b/src/tools/rust-analyzer/crates/vfs/Cargo.toml index 802a300060fa..3ae3dc83ca9b 100644 --- a/src/tools/rust-analyzer/crates/vfs/Cargo.toml +++ b/src/tools/rust-analyzer/crates/vfs/Cargo.toml @@ -15,6 +15,7 @@ doctest = false rustc-hash = "1.1.0" fst = "0.4.7" indexmap = "1.9.1" +nohash-hasher.workspace = true paths.workspace = true stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/vfs/src/file_set.rs b/src/tools/rust-analyzer/crates/vfs/src/file_set.rs index 700aebe0b34f..0392ef3cebe9 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/file_set.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/file_set.rs @@ -5,8 +5,8 @@ use std::fmt; use fst::{IntoStreamer, Streamer}; +use nohash_hasher::IntMap; use rustc_hash::FxHashMap; -use stdx::hash::NoHashHashMap; use crate::{AnchoredPath, FileId, Vfs, VfsPath}; @@ -14,7 +14,7 @@ #[derive(Default, Clone, Eq, PartialEq)] pub struct FileSet { files: FxHashMap, - paths: NoHashHashMap, + paths: IntMap, } impl FileSet { diff --git a/src/tools/rust-analyzer/crates/vfs/src/lib.rs b/src/tools/rust-analyzer/crates/vfs/src/lib.rs index 14972d290741..fe3dfe619686 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/lib.rs @@ -62,7 +62,8 @@ #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct FileId(pub u32); -impl stdx::hash::NoHashHashable for FileId {} +/// safe because `FileId` is a newtype of `u32` +impl nohash_hasher::IsEnabled for FileId {} /// Storage for all files read by rust-analyzer. /// @@ -108,13 +109,6 @@ pub enum ChangeKind { } impl Vfs { - /// Amount of files currently stored. - /// - /// Note that this includes deleted files. - pub fn len(&self) -> usize { - self.data.len() - } - /// Id of the given path if it exists in the `Vfs` and is not deleted. pub fn file_id(&self, path: &VfsPath) -> Option { self.interner.get(path).filter(|&it| self.get(it).is_some()) @@ -139,6 +133,11 @@ pub fn file_contents(&self, file_id: FileId) -> &[u8] { self.get(file_id).as_deref().unwrap() } + /// Returns the overall memory usage for the stored files. + pub fn memory_usage(&self) -> usize { + self.data.iter().flatten().map(|d| d.capacity()).sum() + } + /// Returns an iterator over the stored ids and their corresponding paths. /// /// This will skip deleted files. @@ -158,16 +157,18 @@ pub fn iter(&self) -> impl Iterator + '_ { /// /// If the path does not currently exists in the `Vfs`, allocates a new /// [`FileId`] for it. - pub fn set_file_contents(&mut self, path: VfsPath, contents: Option>) -> bool { + pub fn set_file_contents(&mut self, path: VfsPath, mut contents: Option>) -> bool { let file_id = self.alloc_file_id(path); - let change_kind = match (&self.get(file_id), &contents) { + let change_kind = match (self.get(file_id), &contents) { (None, None) => return false, (Some(old), Some(new)) if old == new => return false, (None, Some(_)) => ChangeKind::Create, (Some(_), None) => ChangeKind::Delete, (Some(_), Some(_)) => ChangeKind::Modify, }; - + if let Some(contents) = &mut contents { + contents.shrink_to_fit(); + } *self.get_mut(file_id) = contents; self.changes.push(ChangedFile { file_id, change_kind }); true diff --git a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs index 38501a8ba5a3..d327f2edf144 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs @@ -107,10 +107,7 @@ pub fn parent(&self) -> Option { /// Returns `self`'s base name and file extension. pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { match &self.0 { - VfsPathRepr::PathBuf(p) => Some(( - p.file_stem()?.to_str()?, - p.extension().and_then(|extension| extension.to_str()), - )), + VfsPathRepr::PathBuf(p) => p.name_and_extension(), VfsPathRepr::VirtualPath(p) => p.name_and_extension(), } } diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md index de1422032088..bc58aa7220dc 100644 --- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ [^\n]+\n/m, ""); } - let value; - if (errorCode) { - if (typeof diag.code === "string" || typeof diag.code === "number") { - value = diag.code; - } else { - value = diag.code?.value; - } - } diag.code = { target: vscode.Uri.from({ scheme: diagnostics.URI_SCHEME, @@ -153,7 +210,8 @@ export async function createClient( fragment: uri.toString(), query: idx.toString(), }), - value: value ?? "Click for full compiler diagnostic", + value: + errorCode && value ? value : "Click for full compiler diagnostic", }; } }); @@ -308,6 +366,7 @@ export async function createClient( // To turn on all proposed features use: client.registerProposedFeatures(); client.registerFeature(new ExperimentalFeatures()); + client.registerFeature(new OverrideFeatures()); return client; } @@ -343,6 +402,25 @@ class ExperimentalFeatures implements lc.StaticFeature { dispose(): void {} } +class OverrideFeatures implements lc.StaticFeature { + getState(): lc.FeatureState { + return { kind: "static" }; + } + fillClientCapabilities(capabilities: lc.ClientCapabilities): void { + // Force disable `augmentsSyntaxTokens`, VSCode's textmate grammar is somewhat incomplete + // making the experience generally worse + const caps = capabilities.textDocument?.semanticTokens; + if (caps) { + caps.augmentsSyntaxTokens = false; + } + } + initialize( + _capabilities: lc.ServerCapabilities, + _documentSelector: lc.DocumentSelector | undefined + ): void {} + dispose(): void {} +} + function isCodeActionWithoutEditsAndCommands(value: any): boolean { const candidate: lc.CodeAction = value; return ( diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts index 8a953577e99d..98ccd50dc040 100644 --- a/src/tools/rust-analyzer/editors/code/src/commands.ts +++ b/src/tools/rust-analyzer/editors/code/src/commands.ts @@ -8,10 +8,18 @@ import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets"; import { spawnSync } from "child_process"; import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run"; import { AstInspector } from "./ast_inspector"; -import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from "./util"; +import { + isRustDocument, + isCargoTomlDocument, + sleep, + isRustEditor, + RustEditor, + RustDocument, +} from "./util"; import { startDebugSession, makeDebugConfig } from "./debug"; import { LanguageClient } from "vscode-languageclient/node"; import { LINKED_COMMANDS } from "./client"; +import { DependencyId } from "./dependencies_provider"; export * from "./ast_inspector"; export * from "./run"; @@ -89,7 +97,13 @@ export function shuffleCrateGraph(ctx: CtxInit): Cmd { export function triggerParameterHints(_: CtxInit): Cmd { return async () => { - await vscode.commands.executeCommand("editor.action.triggerParameterHints"); + const parameterHintsEnabled = vscode.workspace + .getConfiguration("editor") + .get("parameterHints.enabled"); + + if (parameterHintsEnabled) { + await vscode.commands.executeCommand("editor.action.triggerParameterHints"); + } }; } @@ -260,6 +274,71 @@ export function openCargoToml(ctx: CtxInit): Cmd { }; } +export function revealDependency(ctx: CtxInit): Cmd { + return async (editor: RustEditor) => { + if (!ctx.dependencies?.isInitialized()) { + return; + } + const documentPath = editor.document.uri.fsPath; + const dep = ctx.dependencies?.getDependency(documentPath); + if (dep) { + await ctx.treeView?.reveal(dep, { select: true, expand: true }); + } else { + await revealParentChain(editor.document, ctx); + } + }; +} + +/** + * This function calculates the parent chain of a given file until it reaches it crate root contained in ctx.dependencies. + * This is need because the TreeView is Lazy, so at first it only has the root dependencies: For example if we have the following crates: + * - core + * - alloc + * - std + * + * if I want to reveal alloc/src/str.rs, I have to: + + * 1. reveal every children of alloc + * - core + * - alloc\ + *  |-beches\ + *  |-src\ + *  |- ... + * - std + * 2. reveal every children of src: + * core + * alloc\ + *  |-beches\ + *  |-src\ + *   |- lib.rs\ + *   |- str.rs <------- FOUND IT!\ + *   |- ...\ + *  |- ...\ + * std + */ +async function revealParentChain(document: RustDocument, ctx: CtxInit) { + let documentPath = document.uri.fsPath; + const maxDepth = documentPath.split(path.sep).length - 1; + const parentChain: DependencyId[] = [{ id: documentPath.toLowerCase() }]; + do { + documentPath = path.dirname(documentPath); + parentChain.push({ id: documentPath.toLowerCase() }); + if (parentChain.length >= maxDepth) { + // this is an odd case that can happen when we change a crate version but we'd still have + // a open file referencing the old version + return; + } + } while (!ctx.dependencies?.contains(documentPath)); + parentChain.reverse(); + for (const idx in parentChain) { + await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true }); + } +} + +export async function execRevealDependency(e: RustEditor): Promise { + await vscode.commands.executeCommand("rust-analyzer.revealDependency", e); +} + export function ssr(ctx: CtxInit): Cmd { return async () => { const editor = vscode.window.activeTextEditor; @@ -416,8 +495,20 @@ export function syntaxTree(ctx: CtxInit): Cmd { function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd { const viewXir = xir === "hir" ? "viewHir" : "viewMir"; const requestType = xir === "hir" ? ra.viewHir : ra.viewMir; + const uri = `rust-analyzer-${xir}://${viewXir}/${xir}.rs`; + const scheme = `rust-analyzer-${xir}`; + return viewFileUsingTextDocumentContentProvider(ctx, requestType, uri, scheme, true); +} + +function viewFileUsingTextDocumentContentProvider( + ctx: CtxInit, + requestType: lc.RequestType, + uri: string, + scheme: string, + shouldUpdate: boolean +): Cmd { const tdcp = new (class implements vscode.TextDocumentContentProvider { - readonly uri = vscode.Uri.parse(`rust-analyzer-${xir}://${viewXir}/${xir}.rs`); + readonly uri = vscode.Uri.parse(uri); readonly eventEmitter = new vscode.EventEmitter(); constructor() { vscode.workspace.onDidChangeTextDocument( @@ -433,14 +524,14 @@ function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd { } private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { - if (isRustDocument(event.document)) { + if (isRustDocument(event.document) && shouldUpdate) { // We need to order this after language server updates, but there's no API for that. // Hence, good old sleep(). void sleep(10).then(() => this.eventEmitter.fire(this.uri)); } } private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) { - if (editor && isRustEditor(editor)) { + if (editor && isRustEditor(editor) && shouldUpdate) { this.eventEmitter.fire(this.uri); } } @@ -467,9 +558,7 @@ function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd { } })(); - ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider(`rust-analyzer-${xir}`, tdcp) - ); + ctx.pushExtCleanup(vscode.workspace.registerTextDocumentContentProvider(scheme, tdcp)); return async () => { const document = await vscode.workspace.openTextDocument(tdcp.uri); @@ -495,6 +584,20 @@ export function viewMir(ctx: CtxInit): Cmd { return viewHirOrMir(ctx, "mir"); } +// Opens the virtual file that will show the MIR of the function containing the cursor position +// +// The contents of the file come from the `TextDocumentContentProvider` +export function interpretFunction(ctx: CtxInit): Cmd { + const uri = `rust-analyzer-interpret-function://interpretFunction/result.log`; + return viewFileUsingTextDocumentContentProvider( + ctx, + ra.interpretFunction, + uri, + `rust-analyzer-interpret-function`, + false + ); +} + export function viewFileText(ctx: CtxInit): Cmd { const tdcp = new (class implements vscode.TextDocumentContentProvider { readonly uri = vscode.Uri.parse("rust-analyzer-file-text://viewFileText/file.rs"); @@ -663,21 +766,26 @@ function crateGraph(ctx: CtxInit, full: boolean): Cmd { - +
`; @@ -699,7 +807,7 @@ export function viewFullCrateGraph(ctx: CtxInit): Cmd { // The contents of the file come from the `TextDocumentContentProvider` export function expandMacro(ctx: CtxInit): Cmd { function codeFormat(expanded: ra.ExpandedMacro): string { - let result = `// Recursive expansion of ${expanded.name}! macro\n`; + let result = `// Recursive expansion of ${expanded.name} macro\n`; result += "// " + "=".repeat(result.length - 3); result += "\n\n"; result += expanded.expansion; @@ -749,6 +857,10 @@ export function reloadWorkspace(ctx: CtxInit): Cmd { return async () => ctx.client.sendRequest(ra.reloadWorkspace); } +export function rebuildProcMacros(ctx: CtxInit): Cmd { + return async () => ctx.client.sendRequest(ra.rebuildProcMacros); +} + export function addProject(ctx: CtxInit): Cmd { return async () => { const discoverProjectCommand = ctx.config.discoverProjectCommand; @@ -757,12 +869,13 @@ export function addProject(ctx: CtxInit): Cmd { } const workspaces: JsonProject[] = await Promise.all( - vscode.workspace.workspaceFolders!.map(async (folder): Promise => { - const rustDocuments = vscode.workspace.textDocuments.filter(isRustDocument); - return discoverWorkspace(rustDocuments, discoverProjectCommand, { - cwd: folder.uri.fsPath, - }); - }) + vscode.workspace.textDocuments + .filter(isRustDocument) + .map(async (file): Promise => { + return discoverWorkspace([file], discoverProjectCommand, { + cwd: path.dirname(file.uri.fsPath), + }); + }) ); ctx.addToDiscoveredWorkspaces(workspaces); diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts index da7c74c28bae..c6d2bcc2b2a9 100644 --- a/src/tools/rust-analyzer/editors/code/src/config.ts +++ b/src/tools/rust-analyzer/editors/code/src/config.ts @@ -21,7 +21,6 @@ export class Config { "serverPath", "server", "files", - "lens", // works as lens.* ].map((opt) => `${this.rootSection}.${opt}`); readonly package: { @@ -70,7 +69,7 @@ export class Config { if (!requiresReloadOpt) return; if (this.restartServerOnConfigChange) { - await vscode.commands.executeCommand("rust-analyzer.reload"); + await vscode.commands.executeCommand("rust-analyzer.restartServer"); return; } @@ -78,7 +77,7 @@ export class Config { const userResponse = await vscode.window.showInformationMessage(message, "Restart now"); if (userResponse) { - const command = "rust-analyzer.reload"; + const command = "rust-analyzer.restartServer"; await vscode.commands.executeCommand(command); } } @@ -285,6 +284,10 @@ export class Config { get useRustcErrorCode() { return this.get("diagnostics.useRustcErrorCode"); } + + get showDependenciesExplorer() { + return this.get("showDependenciesExplorer"); + } } // the optional `cb?` parameter is meant to be used to add additional diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts index c2dca733df8f..a72b5391ff13 100644 --- a/src/tools/rust-analyzer/editors/code/src/ctx.ts +++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts @@ -1,11 +1,13 @@ import * as vscode from "vscode"; import * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; +import * as path from "path"; import { Config, prepareVSCodeConfig } from "./config"; import { createClient } from "./client"; import { executeDiscoverProject, + isDocumentInWorkspace, isRustDocument, isRustEditor, LazyOutputChannel, @@ -13,6 +15,13 @@ import { RustEditor, } from "./util"; import { ServerStatusParams } from "./lsp_ext"; +import { + Dependency, + DependencyFile, + RustDependenciesProvider, + DependencyId, +} from "./dependencies_provider"; +import { execRevealDependency } from "./commands"; import { PersistentState } from "./persistent_state"; import { bootstrap } from "./bootstrap"; import { ExecOptions } from "child_process"; @@ -82,11 +91,22 @@ export class Ctx { private state: PersistentState; private commandFactories: Record; private commandDisposables: Disposable[]; + private unlinkedFiles: vscode.Uri[]; + private _dependencies: RustDependenciesProvider | undefined; + private _treeView: vscode.TreeView | undefined; get client() { return this._client; } + get treeView() { + return this._treeView; + } + + get dependencies() { + return this._dependencies; + } + constructor( readonly extCtx: vscode.ExtensionContext, commandFactories: Record, @@ -94,12 +114,11 @@ export class Ctx { ) { extCtx.subscriptions.push(this); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); - this.statusBar.show(); this.workspace = workspace; this.clientSubscriptions = []; this.commandDisposables = []; this.commandFactories = commandFactories; - + this.unlinkedFiles = []; this.state = new PersistentState(extCtx.globalState); this.config = new Config(extCtx); @@ -191,12 +210,13 @@ export class Ctx { const discoverProjectCommand = this.config.discoverProjectCommand; if (discoverProjectCommand) { const workspaces: JsonProject[] = await Promise.all( - vscode.workspace.workspaceFolders!.map(async (folder): Promise => { - const rustDocuments = vscode.workspace.textDocuments.filter(isRustDocument); - return discoverWorkspace(rustDocuments, discoverProjectCommand, { - cwd: folder.uri.fsPath, - }); - }) + vscode.workspace.textDocuments + .filter(isRustDocument) + .map(async (file): Promise => { + return discoverWorkspace([file], discoverProjectCommand, { + cwd: path.dirname(file.uri.fsPath), + }); + }) ); this.addToDiscoveredWorkspaces(workspaces); @@ -218,7 +238,8 @@ export class Ctx { this.outputChannel, initializationOptions, serverOptions, - this.config + this.config, + this.unlinkedFiles ); this.pushClientCleanup( this._client.onNotification(ra.serverStatus, (params) => @@ -242,6 +263,56 @@ export class Ctx { } await client.start(); this.updateCommands(); + + if (this.config.showDependenciesExplorer) { + this.prepareTreeDependenciesView(client); + } + } + + private prepareTreeDependenciesView(client: lc.LanguageClient) { + const ctxInit: CtxInit = { + ...this, + client: client, + }; + this._dependencies = new RustDependenciesProvider(ctxInit); + this._treeView = vscode.window.createTreeView("rustDependencies", { + treeDataProvider: this._dependencies, + showCollapseAll: true, + }); + + this.pushExtCleanup(this._treeView); + vscode.window.onDidChangeActiveTextEditor(async (e) => { + // we should skip documents that belong to the current workspace + if (this.shouldRevealDependency(e)) { + try { + await execRevealDependency(e); + } catch (reason) { + await vscode.window.showErrorMessage(`Dependency error: ${reason}`); + } + } + }); + + this.treeView?.onDidChangeVisibility(async (e) => { + if (e.visible) { + const activeEditor = vscode.window.activeTextEditor; + if (this.shouldRevealDependency(activeEditor)) { + try { + await execRevealDependency(activeEditor); + } catch (reason) { + await vscode.window.showErrorMessage(`Dependency error: ${reason}`); + } + } + } + }); + } + + private shouldRevealDependency(e: vscode.TextEditor | undefined): e is RustEditor { + return ( + e !== undefined && + isRustEditor(e) && + !isDocumentInWorkspace(e.document) && + (this.treeView?.visible || false) + ); } async restart() { @@ -335,6 +406,7 @@ export class Ctx { setServerStatus(status: ServerStatusParams | { health: "stopped" }) { let icon = ""; const statusBar = this.statusBar; + statusBar.show(); statusBar.tooltip = new vscode.MarkdownString("", true); statusBar.tooltip.isTrusted = true; switch (status.health) { @@ -343,6 +415,7 @@ export class Ctx { statusBar.color = undefined; statusBar.backgroundColor = undefined; statusBar.command = "rust-analyzer.stopServer"; + this.dependencies?.refresh(); break; case "warning": if (status.message) { @@ -378,12 +451,17 @@ export class Ctx { if (statusBar.tooltip.value) { statusBar.tooltip.appendText("\n\n"); } + statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)"); statusBar.tooltip.appendMarkdown( "\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)" ); - statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)"); - statusBar.tooltip.appendMarkdown("\n\n[Restart server](command:rust-analyzer.startServer)"); - statusBar.tooltip.appendMarkdown("[Stop server](command:rust-analyzer.stopServer)"); + statusBar.tooltip.appendMarkdown( + "\n\n[Rebuild Proc Macros](command:rust-analyzer.rebuildProcMacros)" + ); + statusBar.tooltip.appendMarkdown( + "\n\n[Restart server](command:rust-analyzer.restartServer)" + ); + statusBar.tooltip.appendMarkdown("\n\n[Stop server](command:rust-analyzer.stopServer)"); if (!status.quiescent) icon = "$(sync~spin) "; statusBar.text = `${icon}rust-analyzer`; } @@ -400,4 +478,5 @@ export class Ctx { export interface Disposable { dispose(): void; } + export type Cmd = (...args: any[]) => unknown; diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 268b70b4fbbc..cffee1de6af7 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -118,8 +118,8 @@ async function getDebugConfiguration( return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}"); } - const executable = await getDebugExecutable(runnable); const env = prepareEnv(runnable, ctx.config.runnableEnv); + const executable = await getDebugExecutable(runnable, env); let sourceFileMap = debugOptions.sourceFileMap; if (sourceFileMap === "auto") { // let's try to use the default toolchain @@ -156,8 +156,11 @@ async function getDebugConfiguration( return debugConfig; } -async function getDebugExecutable(runnable: ra.Runnable): Promise { - const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput); +async function getDebugExecutable( + runnable: ra.Runnable, + env: Record +): Promise { + const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env); const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); // if we are here, there were no compilation errors. diff --git a/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts b/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts new file mode 100644 index 000000000000..8900aa9a5f35 --- /dev/null +++ b/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts @@ -0,0 +1,148 @@ +import * as vscode from "vscode"; +import * as fspath from "path"; +import * as fs from "fs"; +import { CtxInit } from "./ctx"; +import * as ra from "./lsp_ext"; +import { FetchDependencyListResult } from "./lsp_ext"; + +export class RustDependenciesProvider + implements vscode.TreeDataProvider +{ + dependenciesMap: { [id: string]: Dependency | DependencyFile }; + ctx: CtxInit; + + constructor(ctx: CtxInit) { + this.dependenciesMap = {}; + this.ctx = ctx; + } + + private _onDidChangeTreeData: vscode.EventEmitter< + Dependency | DependencyFile | undefined | null | void + > = new vscode.EventEmitter(); + + readonly onDidChangeTreeData: vscode.Event< + Dependency | DependencyFile | undefined | null | void + > = this._onDidChangeTreeData.event; + + getDependency(filePath: string): Dependency | DependencyFile | undefined { + return this.dependenciesMap[filePath.toLowerCase()]; + } + + contains(filePath: string): boolean { + return filePath.toLowerCase() in this.dependenciesMap; + } + + isInitialized(): boolean { + return Object.keys(this.dependenciesMap).length !== 0; + } + + refresh(): void { + this.dependenciesMap = {}; + this._onDidChangeTreeData.fire(); + } + + getParent?( + element: Dependency | DependencyFile + ): vscode.ProviderResult { + if (element instanceof Dependency) return undefined; + return element.parent; + } + + getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable { + if (element.id! in this.dependenciesMap) return this.dependenciesMap[element.id!]; + return element; + } + + getChildren( + element?: Dependency | DependencyFile + ): vscode.ProviderResult { + return new Promise((resolve, _reject) => { + if (!vscode.workspace.workspaceFolders) { + void vscode.window.showInformationMessage("No dependency in empty workspace"); + return Promise.resolve([]); + } + if (element) { + const files = fs.readdirSync(element.dependencyPath).map((fileName) => { + const filePath = fspath.join(element.dependencyPath, fileName); + const collapsibleState = fs.lstatSync(filePath).isDirectory() + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None; + const dep = new DependencyFile(fileName, filePath, element, collapsibleState); + this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep; + return dep; + }); + return resolve(files); + } else { + return resolve(this.getRootDependencies()); + } + }); + } + + private async getRootDependencies(): Promise { + const dependenciesResult: FetchDependencyListResult = await this.ctx.client.sendRequest( + ra.fetchDependencyList, + {} + ); + const crates = dependenciesResult.crates; + + return crates + .map((crate) => { + const dep = this.toDep(crate.name || "unknown", crate.version || "", crate.path); + this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep; + return dep; + }) + .sort((a, b) => { + return a.label.localeCompare(b.label); + }); + } + + private toDep(moduleName: string, version: string, path: string): Dependency { + return new Dependency( + moduleName, + version, + vscode.Uri.parse(path).fsPath, + vscode.TreeItemCollapsibleState.Collapsed + ); + } +} + +export class Dependency extends vscode.TreeItem { + constructor( + public readonly label: string, + private version: string, + readonly dependencyPath: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState + ) { + super(label, collapsibleState); + this.resourceUri = vscode.Uri.file(dependencyPath); + this.id = this.resourceUri.fsPath.toLowerCase(); + this.description = this.version; + if (this.version) { + this.tooltip = `${this.label}-${this.version}`; + } else { + this.tooltip = this.label; + } + } +} + +export class DependencyFile extends vscode.TreeItem { + constructor( + readonly label: string, + readonly dependencyPath: string, + readonly parent: Dependency | DependencyFile, + public readonly collapsibleState: vscode.TreeItemCollapsibleState + ) { + super(vscode.Uri.file(dependencyPath), collapsibleState); + this.id = this.resourceUri!.fsPath.toLowerCase(); + const isDir = fs.lstatSync(this.resourceUri!.fsPath).isDirectory(); + if (!isDir) { + this.command = { + command: "vscode.open", + title: "Open File", + arguments: [this.resourceUri], + }; + } + } +} + +export type DependencyId = { id: string }; diff --git a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts index 872d7199b838..b72804e510ce 100644 --- a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts +++ b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts @@ -10,12 +10,9 @@ export const hover = new lc.RequestType< HoverParams, (lc.Hover & { actions: CommandLinkGroup[] }) | null, void ->("textDocument/hover"); -export type HoverParams = { position: lc.Position | lc.Range } & Omit< - lc.TextDocumentPositionParams, - "position" -> & - lc.WorkDoneProgressParams; +>(lc.HoverRequest.method); +export type HoverParams = { position: lc.Position | lc.Range } & Omit; + export type CommandLink = { /** * A tooltip for the command, when represented in the UI. @@ -43,6 +40,7 @@ export const relatedTests = new lc.RequestType("rust-analyzer/reloadWorkspace"); +export const rebuildProcMacros = new lc.RequestType0("rust-analyzer/rebuildProcMacros"); export const runFlycheck = new lc.NotificationType<{ textDocument: lc.TextDocumentIdentifier | null; @@ -63,12 +61,47 @@ export const viewHir = new lc.RequestType( "rust-analyzer/viewMir" ); +export const interpretFunction = new lc.RequestType( + "rust-analyzer/interpretFunction" +); export const viewItemTree = new lc.RequestType( "rust-analyzer/viewItemTree" ); export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier }; +export interface FetchDependencyListParams {} + +export interface FetchDependencyListResult { + crates: { + name: string | undefined; + version: string | undefined; + path: string; + }[]; +} + +export const fetchDependencyList = new lc.RequestType< + FetchDependencyListParams, + FetchDependencyListResult, + void +>("rust-analyzer/fetchDependencyList"); + +export interface FetchDependencyGraphParams {} + +export interface FetchDependencyGraphResult { + crates: { + name: string; + version: string; + path: string; + }[]; +} + +export const fetchDependencyGraph = new lc.RequestType< + FetchDependencyGraphParams, + FetchDependencyGraphResult, + void +>("rust-analyzer/fetchDependencyGraph"); + export type ExpandMacroParams = { textDocument: lc.TextDocumentIdentifier; position: lc.Position; diff --git a/src/tools/rust-analyzer/editors/code/src/main.ts b/src/tools/rust-analyzer/editors/code/src/main.ts index d5de00561b12..be9bc9d363ce 100644 --- a/src/tools/rust-analyzer/editors/code/src/main.ts +++ b/src/tools/rust-analyzer/editors/code/src/main.ts @@ -120,13 +120,11 @@ function createCommands(): Record { enabled: commands.onEnter, disabled: (_) => () => vscode.commands.executeCommand("default:type", { text: "\n" }), }, - reload: { + restartServer: { enabled: (ctx) => async () => { - void vscode.window.showInformationMessage("Reloading rust-analyzer..."); await ctx.restart(); }, disabled: (ctx) => async () => { - void vscode.window.showInformationMessage("Reloading rust-analyzer..."); await ctx.start(); }, }, @@ -153,6 +151,7 @@ function createCommands(): Record { memoryUsage: { enabled: commands.memoryUsage }, shuffleCrateGraph: { enabled: commands.shuffleCrateGraph }, reloadWorkspace: { enabled: commands.reloadWorkspace }, + rebuildProcMacros: { enabled: commands.rebuildProcMacros }, addProject: { enabled: commands.addProject }, matchingBrace: { enabled: commands.matchingBrace }, joinLines: { enabled: commands.joinLines }, @@ -160,6 +159,7 @@ function createCommands(): Record { syntaxTree: { enabled: commands.syntaxTree }, viewHir: { enabled: commands.viewHir }, viewMir: { enabled: commands.viewMir }, + interpretFunction: { enabled: commands.interpretFunction }, viewFileText: { enabled: commands.viewFileText }, viewItemTree: { enabled: commands.viewItemTree }, viewCrateGraph: { enabled: commands.viewCrateGraph }, @@ -190,5 +190,6 @@ function createCommands(): Record { showReferences: { enabled: commands.showReferences }, triggerParameterHints: { enabled: commands.triggerParameterHints }, openLogs: { enabled: commands.openLogs }, + revealDependency: { enabled: commands.revealDependency }, }; } diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts index 35627e2fc6be..623fb102953b 100644 --- a/src/tools/rust-analyzer/editors/code/src/run.ts +++ b/src/tools/rust-analyzer/editors/code/src/run.ts @@ -157,7 +157,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise cargoTask.presentationOptions.clear = true; // Sadly, this doesn't prevent focus stealing if the terminal is currently - // hidden, and will become revealed due to task exucution. + // hidden, and will become revealed due to task execution. cargoTask.presentationOptions.focus = false; return cargoTask; diff --git a/src/tools/rust-analyzer/editors/code/src/tasks.ts b/src/tools/rust-analyzer/editors/code/src/tasks.ts index e6239deeb21a..d6509d9aa6e7 100644 --- a/src/tools/rust-analyzer/editors/code/src/tasks.ts +++ b/src/tools/rust-analyzer/editors/code/src/tasks.ts @@ -128,7 +128,7 @@ export async function buildCargoTask( name, TASK_SOURCE, exec, - ["$rustc"] + ["$rustc", "$rust-panic"] ); } diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts index eb70b88871e3..917a1d6b0997 100644 --- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts +++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts @@ -18,7 +18,11 @@ export interface ArtifactSpec { } export class Cargo { - constructor(readonly rootFolder: string, readonly output: vscode.OutputChannel) {} + constructor( + readonly rootFolder: string, + readonly output: vscode.OutputChannel, + readonly env: Record + ) {} // Made public for testing purposes static artifactSpec(args: readonly string[]): ArtifactSpec { @@ -102,6 +106,7 @@ export class Cargo { const cargo = cp.spawn(path, cargoArgs, { stdio: ["ignore", "pipe", "pipe"], cwd: this.rootFolder, + env: this.env, }); cargo.on("error", (err) => reject(new Error(`could not launch cargo: ${err}`))); diff --git a/src/tools/rust-analyzer/editors/code/src/util.ts b/src/tools/rust-analyzer/editors/code/src/util.ts index 922fbcbcf35a..b6b779e26601 100644 --- a/src/tools/rust-analyzer/editors/code/src/util.ts +++ b/src/tools/rust-analyzer/editors/code/src/util.ts @@ -112,6 +112,19 @@ export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { return isRustDocument(editor.document); } +export function isDocumentInWorkspace(document: RustDocument): boolean { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + return false; + } + for (const folder of workspaceFolders) { + if (document.uri.fsPath.startsWith(folder.uri.fsPath)) { + return true; + } + } + return false; +} + export function isValidExecutable(path: string): boolean { log.debug("Checking availability of a binary at", path); diff --git a/src/tools/rust-analyzer/lib/README.md b/src/tools/rust-analyzer/lib/README.md index 6b2eeac2c0d7..ed55e31d6bb7 100644 --- a/src/tools/rust-analyzer/lib/README.md +++ b/src/tools/rust-analyzer/lib/README.md @@ -1,2 +1,5 @@ -Crates in this directory are published to crates.io and obey semver. -They *could* live in a separate repo, but we want to experiment with a monorepo setup. +# lib + +Crates in this directory are published to [crates.io](https://crates.io) and obey semver. + +They _could_ live in a separate repo, but we want to experiment with a monorepo setup. diff --git a/src/tools/rust-analyzer/lib/la-arena/src/lib.rs b/src/tools/rust-analyzer/lib/la-arena/src/lib.rs index ccaaf3991769..5107f294394b 100644 --- a/src/tools/rust-analyzer/lib/la-arena/src/lib.rs +++ b/src/tools/rust-analyzer/lib/la-arena/src/lib.rs @@ -4,8 +4,9 @@ #![warn(missing_docs)] use std::{ - fmt, + cmp, fmt, hash::{Hash, Hasher}, + iter::{Enumerate, FusedIterator}, marker::PhantomData, ops::{Index, IndexMut, Range, RangeInclusive}, }; @@ -17,13 +18,27 @@ #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RawIdx(u32); +impl RawIdx { + /// Constructs a [`RawIdx`] from a u32. + pub const fn from_u32(u32: u32) -> Self { + RawIdx(u32) + } + + /// Deconstructs a [`RawIdx`] into the underlying u32. + pub const fn into_u32(self) -> u32 { + self.0 + } +} + impl From for u32 { + #[inline] fn from(raw: RawIdx) -> u32 { raw.0 } } impl From for RawIdx { + #[inline] fn from(idx: u32) -> RawIdx { RawIdx(idx) } @@ -47,6 +62,18 @@ pub struct Idx { _ty: PhantomData T>, } +impl Ord for Idx { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.raw.cmp(&other.raw) + } +} + +impl PartialOrd for Idx { + fn partial_cmp(&self, other: &Self) -> Option { + self.raw.partial_cmp(&other.raw) + } +} + impl Clone for Idx { fn clone(&self) -> Self { *self @@ -79,12 +106,12 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { impl Idx { /// Creates a new index from a [`RawIdx`]. - pub fn from_raw(raw: RawIdx) -> Self { + pub const fn from_raw(raw: RawIdx) -> Self { Idx { raw, _ty: PhantomData } } /// Converts this index into the underlying [`RawIdx`]. - pub fn into_raw(self) -> RawIdx { + pub const fn into_raw(self) -> RawIdx { self.raw } } @@ -147,13 +174,46 @@ pub fn new_inclusive(range: RangeInclusive>) -> Self { pub fn is_empty(&self) -> bool { self.range.is_empty() } + + /// Returns the start of the index range. + pub fn start(&self) -> Idx { + Idx::from_raw(RawIdx::from(self.range.start)) + } + + /// Returns the end of the index range. + pub fn end(&self) -> Idx { + Idx::from_raw(RawIdx::from(self.range.end)) + } } impl Iterator for IdxRange { type Item = Idx; + fn next(&mut self) -> Option { self.range.next().map(|raw| Idx::from_raw(raw.into())) } + + fn size_hint(&self) -> (usize, Option) { + self.range.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.range.count() + } + + fn last(self) -> Option + where + Self: Sized, + { + self.range.last().map(|raw| Idx::from_raw(raw.into())) + } + + fn nth(&mut self, n: usize) -> Option { + self.range.nth(n).map(|raw| Idx::from_raw(raw.into())) + } } impl DoubleEndedIterator for IdxRange { @@ -162,6 +222,10 @@ fn next_back(&mut self) -> Option { } } +impl ExactSizeIterator for IdxRange {} + +impl FusedIterator for IdxRange {} + impl fmt::Debug for IdxRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple(&format!("IdxRange::<{}>", std::any::type_name::())) @@ -280,6 +344,21 @@ pub fn alloc(&mut self, value: T) -> Idx { idx } + /// Densely allocates multiple values, returning the values’ index range. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let range = arena.alloc_many(0..4); + /// + /// assert_eq!(arena[range], [0, 1, 2, 3]); + /// ``` + pub fn alloc_many>(&mut self, iter: II) -> IdxRange { + let start = self.next_idx(); + self.extend(iter); + let end = self.next_idx(); + IdxRange::new(start..end) + } + /// Returns an iterator over the arena’s elements. /// /// ``` @@ -295,7 +374,7 @@ pub fn alloc(&mut self, value: T) -> Idx { /// ``` pub fn iter( &self, - ) -> impl Iterator, &T)> + ExactSizeIterator + DoubleEndedIterator { + ) -> impl Iterator, &T)> + ExactSizeIterator + DoubleEndedIterator + Clone { self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) } @@ -335,7 +414,7 @@ pub fn iter_mut( /// assert_eq!(iterator.next(), Some(&40)); /// assert_eq!(iterator.next(), Some(&60)); /// ``` - pub fn values(&mut self) -> impl Iterator + ExactSizeIterator + DoubleEndedIterator { + pub fn values(&self) -> impl Iterator + ExactSizeIterator + DoubleEndedIterator { self.data.iter() } @@ -410,3 +489,32 @@ fn from_iter(iter: I) -> Self Arena { data: Vec::from_iter(iter) } } } + +/// An iterator over the arena’s elements. +pub struct IntoIter(Enumerate< as IntoIterator>::IntoIter>); + +impl Iterator for IntoIter { + type Item = (Idx, T); + + fn next(&mut self) -> Option { + self.0.next().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) + } +} + +impl IntoIterator for Arena { + type Item = (Idx, T); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter(self.data.into_iter().enumerate()) + } +} + +impl Extend for Arena { + fn extend>(&mut self, iter: II) { + for t in iter { + self.alloc(t); + } + } +} diff --git a/src/tools/rust-analyzer/lib/la-arena/src/map.rs b/src/tools/rust-analyzer/lib/la-arena/src/map.rs index 7fff2b09c97b..750f345b5398 100644 --- a/src/tools/rust-analyzer/lib/la-arena/src/map.rs +++ b/src/tools/rust-analyzer/lib/la-arena/src/map.rs @@ -1,3 +1,4 @@ +use std::iter::Enumerate; use std::marker::PhantomData; use crate::Idx; @@ -72,17 +73,17 @@ pub fn get_mut(&mut self, idx: Idx) -> Option<&mut V> { } /// Returns an iterator over the values in the map. - pub fn values(&self) -> impl Iterator { + pub fn values(&self) -> impl Iterator + DoubleEndedIterator { self.v.iter().filter_map(|o| o.as_ref()) } /// Returns an iterator over mutable references to the values in the map. - pub fn values_mut(&mut self) -> impl Iterator { + pub fn values_mut(&mut self) -> impl Iterator + DoubleEndedIterator { self.v.iter_mut().filter_map(|o| o.as_mut()) } /// Returns an iterator over the arena indexes and values in the map. - pub fn iter(&self) -> impl Iterator, &V)> { + pub fn iter(&self) -> impl Iterator, &V)> + DoubleEndedIterator { self.v.iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_ref()?))) } @@ -94,12 +95,6 @@ pub fn iter_mut(&mut self) -> impl Iterator, &mut V)> { .filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_mut()?))) } - /// Returns an iterator over the arena indexes and values in the map. - // FIXME: Implement `IntoIterator` trait. - pub fn into_iter(self) -> impl Iterator, V)> { - self.v.into_iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o?))) - } - /// Gets the given key's corresponding entry in the map for in-place manipulation. pub fn entry(&mut self, idx: Idx) -> Entry<'_, Idx, V> { let idx = Self::to_idx(idx); @@ -154,6 +149,63 @@ fn from_iter, T)>>(iter: I) -> Self { } } +pub struct ArenaMapIter { + iter: Enumerate>>, + _ty: PhantomData, +} + +impl IntoIterator for ArenaMap, V> { + type Item = (Idx, V); + + type IntoIter = ArenaMapIter, V>; + + fn into_iter(self) -> Self::IntoIter { + let iter = self.v.into_iter().enumerate(); + Self::IntoIter { iter, _ty: PhantomData } + } +} + +impl ArenaMapIter, V> { + fn mapper((idx, o): (usize, Option)) -> Option<(Idx, V)> { + Some((ArenaMap::, V>::from_idx(idx), o?)) + } +} + +impl Iterator for ArenaMapIter, V> { + type Item = (Idx, V); + + #[inline] + fn next(&mut self) -> Option { + for next in self.iter.by_ref() { + match Self::mapper(next) { + Some(r) => return Some(r), + None => continue, + } + } + + None + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl DoubleEndedIterator for ArenaMapIter, V> { + #[inline] + fn next_back(&mut self) -> Option { + while let Some(next_back) = self.iter.next_back() { + match Self::mapper(next_back) { + Some(r) => return Some(r), + None => continue, + } + } + + None + } +} + /// A view into a single entry in a map, which may either be vacant or occupied. /// /// This `enum` is constructed from the [`entry`] method on [`ArenaMap`]. diff --git a/src/tools/rust-analyzer/lib/line-index/Cargo.toml b/src/tools/rust-analyzer/lib/line-index/Cargo.toml new file mode 100644 index 000000000000..019ad3a53ba0 --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "line-index" +version = "0.1.0-pre.1" +description = "Maps flat `TextSize` offsets to/from `(line, column)` representation." +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/line-index" +edition = "2021" + +[dependencies] +text-size.workspace = true +nohash-hasher.workspace = true diff --git a/src/tools/rust-analyzer/lib/line-index/src/lib.rs b/src/tools/rust-analyzer/lib/line-index/src/lib.rs new file mode 100644 index 000000000000..ad67d3f246ec --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/src/lib.rs @@ -0,0 +1,237 @@ +//! See [`LineIndex`]. + +#![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)] + +#[cfg(test)] +mod tests; + +use nohash_hasher::IntMap; + +pub use text_size::{TextRange, TextSize}; + +/// `(line, column)` information in the native, UTF-8 encoding. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LineCol { + /// Zero-based. + pub line: u32, + /// Zero-based UTF-8 offset. + pub col: u32, +} + +/// A kind of wide character encoding. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum WideEncoding { + /// UTF-16. + Utf16, + /// UTF-32. + Utf32, +} + +impl WideEncoding { + /// Returns the number of code units it takes to encode `text` in this encoding. + pub fn measure(&self, text: &str) -> usize { + match self { + WideEncoding::Utf16 => text.encode_utf16().count(), + WideEncoding::Utf32 => text.chars().count(), + } + } +} + +/// `(line, column)` information in wide encodings. +/// +/// See [`WideEncoding`] for the kinds of wide encodings available. +// +// Deliberately not a generic type and different from `LineCol`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WideLineCol { + /// Zero-based. + pub line: u32, + /// Zero-based. + pub col: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct WideChar { + /// Start offset of a character inside a line, zero-based. + start: TextSize, + /// End offset of a character inside a line, zero-based. + end: TextSize, +} + +impl WideChar { + /// Returns the length in 8-bit UTF-8 code units. + fn len(&self) -> TextSize { + self.end - self.start + } + + /// Returns the length in UTF-16 or UTF-32 code units. + fn wide_len(&self, enc: WideEncoding) -> u32 { + match enc { + WideEncoding::Utf16 => { + if self.len() == TextSize::from(4) { + 2 + } else { + 1 + } + } + WideEncoding::Utf32 => 1, + } + } +} + +/// Maps flat [`TextSize`] offsets to/from `(line, column)` representation. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LineIndex { + /// Offset the beginning of each line (except the first, which always has offset 0). + newlines: Box<[TextSize]>, + /// List of non-ASCII characters on each line. + line_wide_chars: IntMap>, + /// The length of the entire text. + len: TextSize, +} + +impl LineIndex { + /// Returns a `LineIndex` for the `text`. + pub fn new(text: &str) -> LineIndex { + let mut newlines = Vec::::with_capacity(16); + let mut line_wide_chars = IntMap::>::default(); + + let mut wide_chars = Vec::::new(); + let mut cur_row = TextSize::from(0); + let mut cur_col = TextSize::from(0); + let mut line = 0u32; + + for c in text.chars() { + let c_len = TextSize::of(c); + cur_row += c_len; + if c == '\n' { + newlines.push(cur_row); + + // Save any wide characters seen in the previous line + if !wide_chars.is_empty() { + let cs = std::mem::take(&mut wide_chars).into_boxed_slice(); + line_wide_chars.insert(line, cs); + } + + // Prepare for processing the next line + cur_col = TextSize::from(0); + line += 1; + continue; + } + + if !c.is_ascii() { + wide_chars.push(WideChar { start: cur_col, end: cur_col + c_len }); + } + + cur_col += c_len; + } + + // Save any wide characters seen in the last line + if !wide_chars.is_empty() { + line_wide_chars.insert(line, wide_chars.into_boxed_slice()); + } + + LineIndex { + newlines: newlines.into_boxed_slice(), + line_wide_chars, + len: TextSize::of(text), + } + } + + /// Transforms the `TextSize` into a `LineCol`. + /// + /// # Panics + /// + /// If the offset is invalid. See [`Self::try_line_col`]. + pub fn line_col(&self, offset: TextSize) -> LineCol { + self.try_line_col(offset).expect("invalid offset") + } + + /// Transforms the `TextSize` into a `LineCol`. + /// + /// Returns `None` if the `offset` was invalid, e.g. if it extends past the end of the text or + /// points to the middle of a multi-byte character. + pub fn try_line_col(&self, offset: TextSize) -> Option { + if offset > self.len { + return None; + } + let line = self.newlines.partition_point(|&it| it <= offset); + let start = self.start_offset(line)?; + let col = offset - start; + let ret = LineCol { line: line as u32, col: col.into() }; + self.line_wide_chars + .get(&ret.line) + .into_iter() + .flat_map(|it| it.iter()) + .all(|it| col <= it.start || it.end <= col) + .then_some(ret) + } + + /// Transforms the `LineCol` into a `TextSize`. + pub fn offset(&self, line_col: LineCol) -> Option { + self.start_offset(line_col.line as usize).map(|start| start + TextSize::from(line_col.col)) + } + + fn start_offset(&self, line: usize) -> Option { + match line.checked_sub(1) { + None => Some(TextSize::from(0)), + Some(it) => self.newlines.get(it).copied(), + } + } + + /// Transforms the `LineCol` with the given `WideEncoding` into a `WideLineCol`. + pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> Option { + let mut col = line_col.col; + if let Some(wide_chars) = self.line_wide_chars.get(&line_col.line) { + for c in wide_chars.iter() { + if u32::from(c.end) <= line_col.col { + col = col.checked_sub(u32::from(c.len()) - c.wide_len(enc))?; + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + Some(WideLineCol { line: line_col.line, col }) + } + + /// Transforms the `WideLineCol` with the given `WideEncoding` into a `LineCol`. + pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> Option { + let mut col = line_col.col; + if let Some(wide_chars) = self.line_wide_chars.get(&line_col.line) { + for c in wide_chars.iter() { + if col > u32::from(c.start) { + col = col.checked_add(u32::from(c.len()) - c.wide_len(enc))?; + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + Some(LineCol { line: line_col.line, col }) + } + + /// Given a range [start, end), returns a sorted iterator of non-empty ranges [start, x1), [x1, + /// x2), ..., [xn, end) where all the xi, which are positions of newlines, are inside the range + /// [start, end). + pub fn lines(&self, range: TextRange) -> impl Iterator + '_ { + let lo = self.newlines.partition_point(|&it| it < range.start()); + let hi = self.newlines.partition_point(|&it| it <= range.end()); + let all = std::iter::once(range.start()) + .chain(self.newlines[lo..hi].iter().copied()) + .chain(std::iter::once(range.end())); + + all.clone() + .zip(all.skip(1)) + .map(|(lo, hi)| TextRange::new(lo, hi)) + .filter(|it| !it.is_empty()) + } + + /// Returns the length of the original text. + pub fn len(&self) -> TextSize { + self.len + } +} diff --git a/src/tools/rust-analyzer/lib/line-index/src/tests.rs b/src/tools/rust-analyzer/lib/line-index/src/tests.rs new file mode 100644 index 000000000000..31c01c20ee36 --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/src/tests.rs @@ -0,0 +1,11 @@ +use super::LineIndex; + +#[test] +fn test_empty_index() { + let col_index = LineIndex::new( + " +const C: char = 'x'; +", + ); + assert_eq!(col_index.line_wide_chars.len(), 0); +} diff --git a/src/tools/rust-analyzer/lib/line-index/tests/it.rs b/src/tools/rust-analyzer/lib/line-index/tests/it.rs new file mode 100644 index 000000000000..ce1c0bc6f143 --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/tests/it.rs @@ -0,0 +1,62 @@ +use line_index::{LineCol, LineIndex, TextRange}; + +#[test] +fn test_line_index() { + let text = "hello\nworld"; + let table = [ + (00, 0, 0), + (01, 0, 1), + (05, 0, 5), + (06, 1, 0), + (07, 1, 1), + (08, 1, 2), + (10, 1, 4), + (11, 1, 5), + ]; + + let index = LineIndex::new(text); + for (offset, line, col) in table { + assert_eq!(index.line_col(offset.into()), LineCol { line, col }); + } + + let text = "\nhello\nworld"; + let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)]; + let index = LineIndex::new(text); + for (offset, line, col) in table { + assert_eq!(index.line_col(offset.into()), LineCol { line, col }); + } +} + +#[test] +fn test_char_len() { + assert_eq!('メ'.len_utf8(), 3); + assert_eq!('メ'.len_utf16(), 1); +} + +#[test] +fn test_splitlines() { + fn r(lo: u32, hi: u32) -> TextRange { + TextRange::new(lo.into(), hi.into()) + } + + let text = "a\nbb\nccc\n"; + let line_index = LineIndex::new(text); + + let actual = line_index.lines(r(0, 9)).collect::>(); + let expected = vec![r(0, 2), r(2, 5), r(5, 9)]; + assert_eq!(actual, expected); + + let text = ""; + let line_index = LineIndex::new(text); + + let actual = line_index.lines(r(0, 0)).collect::>(); + let expected = vec![]; + assert_eq!(actual, expected); + + let text = "\n"; + let line_index = LineIndex::new(text); + + let actual = line_index.lines(r(0, 1)).collect::>(); + let expected = vec![r(0, 1)]; + assert_eq!(actual, expected) +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml index 6e32e3960526..e78a9d2eb167 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml +++ b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" [dependencies] log = "0.4.17" -serde_json = "1.0.86" -serde = { version = "1.0.144", features = ["derive"] } +serde_json.workspace = true +serde.workspace = true crossbeam-channel = "0.5.6" [dev-dependencies] diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/error.rs b/src/tools/rust-analyzer/lib/lsp-server/src/error.rs index 4c934d9ecca5..755b3fd95962 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/src/error.rs +++ b/src/tools/rust-analyzer/lib/lsp-server/src/error.rs @@ -2,7 +2,7 @@ use crate::{Notification, Request}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ProtocolError(pub(crate) String); impl std::error::Error for ProtocolError {} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs b/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs index beccde40a897..affab60a2278 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs +++ b/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs @@ -126,6 +126,9 @@ pub fn initialize_start(&self) -> Result<(RequestId, serde_json::Value), Protoco self.sender.send(resp.into()).unwrap(); continue; } + Ok(Message::Notification(n)) if !n.is_exit() => { + continue; + } Ok(msg) => Err(ProtocolError(format!("expected initialize request, got {msg:?}"))), Err(e) => { Err(ProtocolError(format!("expected initialize request, got error: {e}"))) @@ -212,3 +215,70 @@ pub fn handle_shutdown(&self, req: &Request) -> Result { Ok(true) } } + +#[cfg(test)] +mod tests { + use crossbeam_channel::unbounded; + use lsp_types::notification::{Exit, Initialized, Notification}; + use lsp_types::request::{Initialize, Request}; + use lsp_types::{InitializeParams, InitializedParams}; + use serde_json::to_value; + + use crate::{Connection, Message, ProtocolError, RequestId}; + + struct TestCase { + test_messages: Vec, + expected_resp: Result<(RequestId, serde_json::Value), ProtocolError>, + } + + fn initialize_start_test(test_case: TestCase) { + let (reader_sender, reader_receiver) = unbounded::(); + let (writer_sender, writer_receiver) = unbounded::(); + let conn = Connection { sender: writer_sender, receiver: reader_receiver }; + + for msg in test_case.test_messages { + assert!(reader_sender.send(msg).is_ok()); + } + + let resp = conn.initialize_start(); + assert_eq!(test_case.expected_resp, resp); + + assert!(writer_receiver.recv_timeout(std::time::Duration::from_secs(1)).is_err()); + } + + #[test] + fn not_exit_notification() { + let notification = crate::Notification { + method: Initialized::METHOD.to_string(), + params: to_value(InitializedParams {}).unwrap(), + }; + + let params_as_value = to_value(InitializeParams::default()).unwrap(); + let req_id = RequestId::from(234); + let request = crate::Request { + id: req_id.clone(), + method: Initialize::METHOD.to_string(), + params: params_as_value.clone(), + }; + + initialize_start_test(TestCase { + test_messages: vec![notification.into(), request.into()], + expected_resp: Ok((req_id, params_as_value)), + }); + } + + #[test] + fn exit_notification() { + let notification = + crate::Notification { method: Exit::METHOD.to_string(), params: to_value(()).unwrap() }; + let notification_msg = Message::from(notification); + + initialize_start_test(TestCase { + test_messages: vec![notification_msg.clone()], + expected_resp: Err(ProtocolError(format!( + "expected initialize request, got {:?}", + notification_msg + ))), + }); + } +}