diff options
Diffstat (limited to 'src')
130 files changed, 3907 insertions, 2188 deletions
diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs index 899e3fd9a45..d4cbbe60921 100644 --- a/src/bootstrap/src/core/build_steps/gcc.rs +++ b/src/bootstrap/src/core/build_steps/gcc.rs @@ -220,21 +220,18 @@ fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) { t!(fs::create_dir_all(install_dir)); // GCC creates files (e.g. symlinks to the downloaded dependencies) - // in the source directory, which does not work with our CI setup, where we mount + // in the source directory, which does not work with our CI/Docker setup, where we mount // source directories as read-only on Linux. - // Therefore, as a part of the build in CI, we first copy the whole source directory - // to the build directory, and perform the build from there. - let src_dir = if builder.config.is_running_on_ci { - let src_dir = builder.gcc_out(target).join("src"); - if src_dir.exists() { - builder.remove_dir(&src_dir); - } - builder.create_dir(&src_dir); - builder.cp_link_r(root, &src_dir); - src_dir - } else { - root.clone() - }; + // And in general, we shouldn't be modifying the source directories if possible, even for local + // builds. + // Therefore, we first copy the whole source directory to the build directory, and perform the + // build from there. + let src_dir = builder.gcc_out(target).join("src"); + if src_dir.exists() { + builder.remove_dir(&src_dir); + } + builder.create_dir(&src_dir); + builder.cp_link_r(root, &src_dir); command(src_dir.join("contrib/download_prerequisites")).current_dir(&src_dir).run(builder); let mut configure_cmd = command(src_dir.join("configure")); diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index 307aa52294b..71fab0e6ae6 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -531,6 +531,14 @@ impl Config { lld_enabled = lld_enabled_toml; std_features = std_features_toml; + if optimize_toml.as_ref().is_some_and(|v| matches!(v, RustOptimize::Bool(false))) { + eprintln!( + "WARNING: setting `optimize` to `false` is known to cause errors and \ + should be considered unsupported. Refer to `bootstrap.example.toml` \ + for more details." + ); + } + optimize = optimize_toml; self.rust_new_symbol_mangling = new_symbol_mangling; set(&mut self.rust_optimize_tests, optimize_tests); diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 6c5e37b51ef..f0c52fe3d1c 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -34,6 +34,8 @@ runners: os: windows-2022 <<: *base-job + # NOTE: windows-2025 has less disk space available than windows-2022, + # because the D drive is missing. - &job-windows-25 os: windows-2025 <<: *base-job @@ -542,13 +544,13 @@ auto: env: RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-sanitizers --enable-profiler SCRIPT: make ci-msvc-py - <<: *job-windows-25 + <<: *job-windows - name: x86_64-msvc-2 env: RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-sanitizers --enable-profiler SCRIPT: make ci-msvc-ps1 - <<: *job-windows-25 + <<: *job-windows # i686-msvc is split into two jobs to run tests in parallel. - name: i686-msvc-1 diff --git a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml index 1e430d8b4e6..ad570ee4595 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml @@ -9,106 +9,12 @@ on: jobs: pull: if: github.repository == 'rust-lang/rustc-dev-guide' - runs-on: ubuntu-latest - outputs: - pr_url: ${{ steps.update-pr.outputs.pr_url }} - permissions: - contents: write - pull-requests: write - steps: - - uses: actions/checkout@v4 - with: - # We need the full history for josh to work - fetch-depth: '0' - - name: Install stable Rust toolchain - run: rustup update stable - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "josh-sync" - # Cache the josh directory with checked out rustc - cache-directories: "/home/runner/.cache/rustc-dev-guide-josh" - - name: Install josh - run: RUSTFLAGS="--cap-lints warn" cargo install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04 - - name: Setup bot git name and email - run: | - git config --global user.name 'The rustc-dev-guide Cronjob Bot' - git config --global user.email 'github-actions@github.com' - - name: Perform rustc-pull - id: rustc-pull - # Turn off -e to disable early exit - shell: bash {0} - run: | - cargo run --manifest-path josh-sync/Cargo.toml -- rustc-pull - exitcode=$? - - # If no pull was performed, we want to mark this job as successful, - # but we do not want to perform the follow-up steps. - if [ $exitcode -eq 0 ]; then - echo "pull_result=pull-finished" >> $GITHUB_OUTPUT - elif [ $exitcode -eq 2 ]; then - echo "pull_result=skipped" >> $GITHUB_OUTPUT - exitcode=0 - fi - - exit ${exitcode} - - name: Push changes to a branch - if: ${{ steps.rustc-pull.outputs.pull_result == 'pull-finished' }} - run: | - # Update a sticky branch that is used only for rustc pulls - BRANCH="rustc-pull" - git switch -c $BRANCH - git push -u origin $BRANCH --force - - name: Create pull request - id: update-pr - if: ${{ steps.rustc-pull.outputs.pull_result == 'pull-finished' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Check if an open pull request for an rustc pull update already exists - # If it does, the previous push has just updated it - # If not, we create it now - RESULT=`gh pr list --author github-actions[bot] --state open -q 'map(select(.title=="Rustc pull update")) | length' --json title` - if [[ "$RESULT" -eq 0 ]]; then - echo "Creating new pull request" - PR_URL=`gh pr create -B master --title 'Rustc pull update' --body 'Latest update from rustc.'` - echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT - else - PR_URL=`gh pr list --author github-actions[bot] --state open -q 'map(select(.title=="Rustc pull update")) | .[0].url' --json url,title` - echo "Updating pull request ${PR_URL}" - echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT - fi - send-zulip-message: - needs: [pull] - if: ${{ !cancelled() }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Compute message - id: create-message - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [ "${{ needs.pull.result }}" == "failure" ]; then - WORKFLOW_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - echo "message=Rustc pull sync failed. Check out the [workflow URL]($WORKFLOW_URL)." >> $GITHUB_OUTPUT - else - CREATED_AT=`gh pr list --author github-actions[bot] --state open -q 'map(select(.title=="Rustc pull update")) | .[0].createdAt' --json createdAt,title` - PR_URL=`gh pr list --author github-actions[bot] --state open -q 'map(select(.title=="Rustc pull update")) | .[0].url' --json url,title` - week_ago=$(date +%F -d '7 days ago') - - # If there is an open PR that is at least a week old, post a message about it - if [[ -n $DATE_GH && $DATE_GH < $week_ago ]]; then - echo "message=A PR with a Rustc pull has been opened for more a week. Check out the [PR](${PR_URL})." >> $GITHUB_OUTPUT - fi - fi - - name: Send a Zulip message about updated PR - if: ${{ steps.create-message.outputs.message != '' }} - uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 - with: - api-key: ${{ secrets.ZULIP_API_TOKEN }} - email: "rustc-dev-guide-gha-notif-bot@rust-lang.zulipchat.com" - organization-url: "https://rust-lang.zulipchat.com" - to: 196385 - type: "stream" - topic: "Subtree sync automation" - content: ${{ steps.create-message.outputs.message }} + uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main + with: + zulip-stream-id: 196385 + zulip-bot-email: "rustc-dev-guide-gha-notif-bot@rust-lang.zulipchat.com" + pr-base-branch: master + branch-name: rustc-pull + secrets: + zulip-api-token: ${{ secrets.ZULIP_API_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/doc/rustc-dev-guide/README.md b/src/doc/rustc-dev-guide/README.md index 0425c15f83c..5932da467ab 100644 --- a/src/doc/rustc-dev-guide/README.md +++ b/src/doc/rustc-dev-guide/README.md @@ -72,49 +72,6 @@ including the `<!-- toc -->` marker at the place where you want the TOC. ## Synchronizing josh subtree with rustc -This repository is linked to `rust-lang/rust` as a [josh](https://josh-project.github.io/josh/intro.html) subtree. You can use the following commands to synchronize the subtree in both directions. +This repository is linked to `rust-lang/rust` as a [josh](https://josh-project.github.io/josh/intro.html) subtree. You can use the [rustc-josh-sync](https://github.com/rust-lang/josh-sync) tool to perform synchronization. -You'll need to install `josh-proxy` locally via - -``` -cargo install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04 -``` -Older versions of `josh-proxy` may not round trip commits losslessly so it is important to install this exact version. - -### Pull changes from `rust-lang/rust` into this repository - -1) Checkout a new branch that will be used to create a PR into `rust-lang/rustc-dev-guide` -2) Run the pull command - ``` - cargo run --manifest-path josh-sync/Cargo.toml rustc-pull - ``` -3) Push the branch to your fork and create a PR into `rustc-dev-guide` - -### Push changes from this repository into `rust-lang/rust` - -NOTE: If you use Git protocol to push to your fork of `rust-lang/rust`, -ensure that you have this entry in your Git config, -else the 2 steps that follow would prompt for a username and password: - -``` -[url "git@github.com:"] -insteadOf = "https://github.com/" -``` - -1) Run the push command to create a branch named `<branch-name>` in a `rustc` fork under the `<gh-username>` account - ``` - cargo run --manifest-path josh-sync/Cargo.toml rustc-push <branch-name> <gh-username> - ``` -2) Create a PR from `<branch-name>` into `rust-lang/rust` - -#### Minimal git config - -For simplicity (ease of implementation purposes), the josh-sync script simply calls out to system git. This means that the git invocation may be influenced by global (or local) git configuration. - -You may observe "Nothing to pull" even if you *know* rustc-pull has something to pull if your global git config sets `fetch.prunetags = true` (and possibly other configurations may cause unexpected outcomes). - -To minimize the likelihood of this happening, you may wish to keep a separate *minimal* git config that *only* has `[user]` entries from global git config, then repoint system git to use the minimal git config instead. E.g. - -``` -GIT_CONFIG_GLOBAL=/path/to/minimal/gitconfig GIT_CONFIG_SYSTEM='' cargo run --manifest-path josh-sync/Cargo.toml -- rustc-pull -``` +You can find a guide on how to perform the synchronization [here](./src/external-repos.md#synchronizing-a-josh-subtree). diff --git a/src/doc/rustc-dev-guide/josh-sync/Cargo.lock b/src/doc/rustc-dev-guide/josh-sync/Cargo.lock deleted file mode 100644 index a8183a740db..00000000000 --- a/src/doc/rustc-dev-guide/josh-sync/Cargo.lock +++ /dev/null @@ -1,430 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" -dependencies = [ - "anstyle", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.5.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "josh-sync" -version = "0.0.0" -dependencies = [ - "anyhow", - "clap", - "directories", - "xshell", -] - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "xshell" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" -dependencies = [ - "xshell-macros", -] - -[[package]] -name = "xshell-macros" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" diff --git a/src/doc/rustc-dev-guide/josh-sync/Cargo.toml b/src/doc/rustc-dev-guide/josh-sync/Cargo.toml deleted file mode 100644 index 1f8bf2a0093..00000000000 --- a/src/doc/rustc-dev-guide/josh-sync/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "josh-sync" -edition = "2024" - -[dependencies] -anyhow = "1.0.95" -clap = { version = "4.5.21", features = ["derive"] } -directories = "5" -xshell = "0.2.6" diff --git a/src/doc/rustc-dev-guide/josh-sync/README.md b/src/doc/rustc-dev-guide/josh-sync/README.md deleted file mode 100644 index a3dd876e8b8..00000000000 --- a/src/doc/rustc-dev-guide/josh-sync/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Git josh sync -This utility serves for syncing the josh git subtree to and from the rust-lang/rust repository. - -See CLI help for usage. diff --git a/src/doc/rustc-dev-guide/josh-sync/src/main.rs b/src/doc/rustc-dev-guide/josh-sync/src/main.rs deleted file mode 100644 index aeedee5be22..00000000000 --- a/src/doc/rustc-dev-guide/josh-sync/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -use clap::Parser; - -use crate::sync::{GitSync, RustcPullError}; - -mod sync; - -#[derive(clap::Parser)] -enum Args { - /// Pull changes from the main `rustc` repository. - /// This creates new commits that should be then merged into `rustc-dev-guide`. - RustcPull, - /// Push changes from `rustc-dev-guide` to the given `branch` of a `rustc` fork under the given - /// GitHub `username`. - /// The pushed branch should then be merged into the `rustc` repository. - RustcPush { branch: String, github_username: String }, -} - -fn main() -> anyhow::Result<()> { - let args = Args::parse(); - let sync = GitSync::from_current_dir()?; - match args { - Args::RustcPull => { - if let Err(error) = sync.rustc_pull(None) { - match error { - RustcPullError::NothingToPull => { - eprintln!("Nothing to pull"); - std::process::exit(2); - } - RustcPullError::PullFailed(error) => { - eprintln!("Pull failure: {error:?}"); - std::process::exit(1); - } - } - } - } - Args::RustcPush { github_username, branch } => { - sync.rustc_push(github_username, branch)?; - } - } - Ok(()) -} diff --git a/src/doc/rustc-dev-guide/josh-sync/src/sync.rs b/src/doc/rustc-dev-guide/josh-sync/src/sync.rs deleted file mode 100644 index ed38d1403a0..00000000000 --- a/src/doc/rustc-dev-guide/josh-sync/src/sync.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::io::Write; -use std::ops::Not; -use std::path::PathBuf; -use std::time::Duration; -use std::{env, net, process}; - -use anyhow::{Context, anyhow, bail}; -use xshell::{Shell, cmd}; - -/// Used for rustc syncs. -const JOSH_FILTER: &str = ":/src/doc/rustc-dev-guide"; -const JOSH_PORT: u16 = 42042; -const UPSTREAM_REPO: &str = "rust-lang/rust"; - -pub enum RustcPullError { - /// No changes are available to be pulled. - NothingToPull, - /// A rustc-pull has failed, probably a git operation error has occurred. - PullFailed(anyhow::Error), -} - -impl<E> From<E> for RustcPullError -where - E: Into<anyhow::Error>, -{ - fn from(error: E) -> Self { - Self::PullFailed(error.into()) - } -} - -pub struct GitSync { - dir: PathBuf, -} - -/// This code was adapted from the miri repository -/// (https://github.com/rust-lang/miri/blob/6a68a79f38064c3bc30617cca4bdbfb2c336b140/miri-script/src/commands.rs#L236). -impl GitSync { - pub fn from_current_dir() -> anyhow::Result<Self> { - Ok(Self { dir: std::env::current_dir()? }) - } - - pub fn rustc_pull(&self, commit: Option<String>) -> Result<(), RustcPullError> { - let sh = Shell::new()?; - sh.change_dir(&self.dir); - let commit = commit.map(Ok).unwrap_or_else(|| { - let rust_repo_head = - cmd!(sh, "git ls-remote https://github.com/{UPSTREAM_REPO}/ HEAD").read()?; - rust_repo_head - .split_whitespace() - .next() - .map(|front| front.trim().to_owned()) - .ok_or_else(|| anyhow!("Could not obtain Rust repo HEAD from remote.")) - })?; - // Make sure the repo is clean. - if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() { - return Err(anyhow::anyhow!( - "working directory must be clean before performing rustc pull" - ) - .into()); - } - // Make sure josh is running. - let josh = Self::start_josh()?; - let josh_url = - format!("http://localhost:{JOSH_PORT}/{UPSTREAM_REPO}.git@{commit}{JOSH_FILTER}.git"); - - let previous_base_commit = sh.read_file("rust-version")?.trim().to_string(); - if previous_base_commit == commit { - return Err(RustcPullError::NothingToPull); - } - - // Update rust-version file. As a separate commit, since making it part of - // the merge has confused the heck out of josh in the past. - // We pass `--no-verify` to avoid running git hooks. - // We do this before the merge so that if there are merge conflicts, we have - // the right rust-version file while resolving them. - sh.write_file("rust-version", format!("{commit}\n"))?; - const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc"; - cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}") - .run() - .context("FAILED to commit rust-version file, something went wrong")?; - - // Fetch given rustc commit. - cmd!(sh, "git fetch {josh_url}") - .run() - .inspect_err(|_| { - // Try to un-do the previous `git commit`, to leave the repo in the state we found it. - cmd!(sh, "git reset --hard HEAD^") - .run() - .expect("FAILED to clean up again after failed `git fetch`, sorry for that"); - }) - .context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?; - - // This should not add any new root commits. So count those before and after merging. - let num_roots = || -> anyhow::Result<u32> { - Ok(cmd!(sh, "git rev-list HEAD --max-parents=0 --count") - .read() - .context("failed to determine the number of root commits")? - .parse::<u32>()?) - }; - let num_roots_before = num_roots()?; - - let sha = - cmd!(sh, "git rev-parse HEAD").output().context("FAILED to get current commit")?.stdout; - - // Merge the fetched commit. - const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc"; - cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}") - .run() - .context("FAILED to merge new commits, something went wrong")?; - - let current_sha = - cmd!(sh, "git rev-parse HEAD").output().context("FAILED to get current commit")?.stdout; - if current_sha == sha { - cmd!(sh, "git reset --hard HEAD^") - .run() - .expect("FAILED to clean up after creating the preparation commit"); - eprintln!( - "No merge was performed, no changes to pull were found. Rolled back the preparation commit." - ); - return Err(RustcPullError::NothingToPull); - } - - // Check that the number of roots did not increase. - if num_roots()? != num_roots_before { - return Err(anyhow::anyhow!( - "Josh created a new root commit. This is probably not the history you want." - ) - .into()); - } - - drop(josh); - Ok(()) - } - - pub fn rustc_push(&self, github_user: String, branch: String) -> anyhow::Result<()> { - let sh = Shell::new()?; - sh.change_dir(&self.dir); - let base = sh.read_file("rust-version")?.trim().to_owned(); - // Make sure the repo is clean. - if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() { - bail!("working directory must be clean before running `rustc-push`"); - } - // Make sure josh is running. - let josh = Self::start_josh()?; - let josh_url = - format!("http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git"); - - // Find a repo we can do our preparation in. - if let Ok(rustc_git) = env::var("RUSTC_GIT") { - // If rustc_git is `Some`, we'll use an existing fork for the branch updates. - sh.change_dir(rustc_git); - } else { - // Otherwise, do this in the local repo. - println!( - "This will pull a copy of the rust-lang/rust history into this checkout, growing it by about 1GB." - ); - print!( - "To avoid that, abort now and set the `RUSTC_GIT` environment variable to an existing rustc checkout. Proceed? [y/N] " - ); - std::io::stdout().flush()?; - let mut answer = String::new(); - std::io::stdin().read_line(&mut answer)?; - if answer.trim().to_lowercase() != "y" { - std::process::exit(1); - } - }; - // Prepare the branch. Pushing works much better if we use as base exactly - // the commit that we pulled from last time, so we use the `rust-version` - // file to find out which commit that would be. - println!("Preparing {github_user}/rust (base: {base})..."); - if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}") - .ignore_stderr() - .read() - .is_ok() - { - println!( - "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again." - ); - std::process::exit(1); - } - cmd!(sh, "git fetch https://github.com/{UPSTREAM_REPO} {base}").run()?; - cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}") - .ignore_stdout() - .ignore_stderr() // silence the "create GitHub PR" message - .run()?; - println!(); - - // Do the actual push. - sh.change_dir(&self.dir); - println!("Pushing changes..."); - cmd!(sh, "git push {josh_url} HEAD:{branch}").run()?; - println!(); - - // Do a round-trip check to make sure the push worked as expected. - cmd!(sh, "git fetch {josh_url} {branch}").ignore_stderr().read()?; - let head = cmd!(sh, "git rev-parse HEAD").read()?; - let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?; - if head != fetch_head { - bail!( - "Josh created a non-roundtrip push! Do NOT merge this into rustc!\n\ - Expected {head}, got {fetch_head}." - ); - } - println!( - "Confirmed that the push round-trips back to rustc-dev-guide properly. Please create a rustc PR:" - ); - println!( - // Open PR with `subtree update` title to silence the `no-merges` triagebot check - " https://github.com/{UPSTREAM_REPO}/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost" - ); - - drop(josh); - Ok(()) - } - - fn start_josh() -> anyhow::Result<impl Drop> { - // Determine cache directory. - let local_dir = { - let user_dirs = - directories::ProjectDirs::from("org", "rust-lang", "rustc-dev-guide-josh").unwrap(); - user_dirs.cache_dir().to_owned() - }; - - // Start josh, silencing its output. - let mut cmd = process::Command::new("josh-proxy"); - cmd.arg("--local").arg(local_dir); - cmd.arg("--remote").arg("https://github.com"); - cmd.arg("--port").arg(JOSH_PORT.to_string()); - cmd.arg("--no-background"); - cmd.stdout(process::Stdio::null()); - cmd.stderr(process::Stdio::null()); - let josh = cmd.spawn().context("failed to start josh-proxy, make sure it is installed")?; - - // Create a wrapper that stops it on drop. - struct Josh(process::Child); - impl Drop for Josh { - fn drop(&mut self) { - #[cfg(unix)] - { - // Try to gracefully shut it down. - process::Command::new("kill") - .args(["-s", "INT", &self.0.id().to_string()]) - .output() - .expect("failed to SIGINT josh-proxy"); - // Sadly there is no "wait with timeout"... so we just give it some time to finish. - std::thread::sleep(Duration::from_millis(100)); - // Now hopefully it is gone. - if self.0.try_wait().expect("failed to wait for josh-proxy").is_some() { - return; - } - } - // If that didn't work (or we're not on Unix), kill it hard. - eprintln!( - "I have to kill josh-proxy the hard way, let's hope this does not break anything." - ); - self.0.kill().expect("failed to SIGKILL josh-proxy"); - } - } - - // Wait until the port is open. We try every 10ms until 1s passed. - for _ in 0..100 { - // This will generally fail immediately when the port is still closed. - let josh_ready = net::TcpStream::connect_timeout( - &net::SocketAddr::from(([127, 0, 0, 1], JOSH_PORT)), - Duration::from_millis(1), - ); - if josh_ready.is_ok() { - return Ok(Josh(josh)); - } - // Not ready yet. - std::thread::sleep(Duration::from_millis(10)); - } - bail!("Even after waiting for 1s, josh-proxy is still not available.") - } -} diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index e444613e631..3f10132b684 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -c96a69059ecc618b519da385a6ccd03155aa0237 +fd2eb391d032181459773f3498c17b198513e0d0 diff --git a/src/doc/rustc-dev-guide/src/autodiff/installation.md b/src/doc/rustc-dev-guide/src/autodiff/installation.md index c9b28dc43a6..a550f6d233e 100644 --- a/src/doc/rustc-dev-guide/src/autodiff/installation.md +++ b/src/doc/rustc-dev-guide/src/autodiff/installation.md @@ -6,14 +6,14 @@ In the near future, `std::autodiff` should become available in nightly builds fo First you need to clone and configure the Rust repository: ```bash -git clone --depth=1 git@github.com:rust-lang/rust.git +git clone git@github.com:rust-lang/rust cd rust ./configure --enable-llvm-link-shared --enable-llvm-plugins --enable-llvm-enzyme --release-channel=nightly --enable-llvm-assertions --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs ``` Afterwards you can build rustc using: ```bash -./x.py build --stage 1 library +./x build --stage 1 library ``` Afterwards rustc toolchain link will allow you to use it through cargo: @@ -25,10 +25,10 @@ rustup toolchain install nightly # enables -Z unstable-options You can then run our test cases: ```bash -./x.py test --stage 1 tests/codegen/autodiff -./x.py test --stage 1 tests/pretty/autodiff -./x.py test --stage 1 tests/ui/autodiff -./x.py test --stage 1 tests/ui/feature-gates/feature-gate-autodiff.rs +./x test --stage 1 tests/codegen/autodiff +./x test --stage 1 tests/pretty/autodiff +./x test --stage 1 tests/ui/autodiff +./x test --stage 1 tests/ui/feature-gates/feature-gate-autodiff.rs ``` Autodiff is still experimental, so if you want to use it in your own projects, you will need to add `lto="fat"` to your Cargo.toml @@ -45,7 +45,7 @@ apt install wget vim python3 git curl libssl-dev pkg-config lld ninja-build cmak ``` Then build rustc in a slightly altered way: ```bash -git clone --depth=1 https://github.com/rust-lang/rust.git +git clone https://github.com/rust-lang/rust cd rust ./configure --enable-llvm-link-shared --enable-llvm-plugins --enable-llvm-enzyme --release-channel=nightly --enable-llvm-assertions --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs ./x dist @@ -66,7 +66,7 @@ We recommend that approach, if you just want to use any of them and have no expe However, if you prefer to just build Enzyme without Rust, then these instructions might help. ```bash -git clone --depth=1 git@github.com:llvm/llvm-project.git +git clone git@github.com:llvm/llvm-project cd llvm-project mkdir build cd build @@ -77,7 +77,7 @@ ninja install This gives you a working LLVM build, now we can continue with building Enzyme. Leave the `llvm-project` folder, and execute the following commands: ```bash -git clone git@github.com:EnzymeAD/Enzyme.git +git clone git@github.com:EnzymeAD/Enzyme cd Enzyme/enzyme mkdir build cd build diff --git a/src/doc/rustc-dev-guide/src/external-repos.md b/src/doc/rustc-dev-guide/src/external-repos.md index f3170c9222d..ecc65b26ab7 100644 --- a/src/doc/rustc-dev-guide/src/external-repos.md +++ b/src/doc/rustc-dev-guide/src/external-repos.md @@ -9,7 +9,7 @@ There are three main ways we use dependencies: As a general rule: - Use crates.io for libraries that could be useful for others in the ecosystem - Use subtrees for tools that depend on compiler internals and need to be updated if there are breaking -changes + changes - Use submodules for tools that are independent of the compiler ## External Dependencies (subtrees) @@ -23,6 +23,8 @@ The following external projects are managed using some form of a `subtree`: * [rust-analyzer](https://github.com/rust-lang/rust-analyzer) * [rustc_codegen_cranelift](https://github.com/rust-lang/rustc_codegen_cranelift) * [rustc-dev-guide](https://github.com/rust-lang/rustc-dev-guide) +* [compiler-builtins](https://github.com/rust-lang/compiler-builtins) +* [stdarch](https://github.com/rust-lang/stdarch) In contrast to `submodule` dependencies (see below for those), the `subtree` dependencies are just regular files and directories which can @@ -34,20 +36,61 @@ implement a new tool feature or test, that should happen in one collective rustc `subtree` dependencies are currently managed by two distinct approaches: * Using `git subtree` - * `clippy` ([sync guide](https://doc.rust-lang.org/nightly/clippy/development/infrastructure/sync.html#performing-the-sync-from-rust-langrust-to-clippy)) - * `portable-simd` ([sync script](https://github.com/rust-lang/portable-simd/blob/master/subtree-sync.sh)) - * `rustfmt` - * `rustc_codegen_cranelift` ([sync script](https://github.com/rust-lang/rustc_codegen_cranelift/blob/113af154d459e41b3dc2c5d7d878e3d3a8f33c69/scripts/rustup.sh#L7)) + * `clippy` ([sync guide](https://doc.rust-lang.org/nightly/clippy/development/infrastructure/sync.html#performing-the-sync-from-rust-langrust-to-clippy)) + * `portable-simd` ([sync script](https://github.com/rust-lang/portable-simd/blob/master/subtree-sync.sh)) + * `rustfmt` + * `rustc_codegen_cranelift` ([sync script](https://github.com/rust-lang/rustc_codegen_cranelift/blob/113af154d459e41b3dc2c5d7d878e3d3a8f33c69/scripts/rustup.sh#L7)) * Using the [josh] tool - * `miri` ([sync guide](https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md#advanced-topic-syncing-with-the-rustc-repo)) - * `rust-analyzer` ([sync script](https://github.com/rust-lang/rust-analyzer/blob/2e13684be123eca7181aa48e043e185d8044a84a/xtask/src/release.rs#L147)) - * `rustc-dev-guide` ([sync guide](https://github.com/rust-lang/rustc-dev-guide#synchronizing-josh-subtree-with-rustc)) + * `miri` ([sync guide](https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md#advanced-topic-syncing-with-the-rustc-repo)) + * `rust-analyzer` ([sync script](https://github.com/rust-lang/rust-analyzer/blob/2e13684be123eca7181aa48e043e185d8044a84a/xtask/src/release.rs#L147)) + * `rustc-dev-guide` ([josh sync](#synchronizing-a-josh-subtree)) + * `compiler-builtins` ([josh sync](#synchronizing-a-josh-subtree)) + * `stdarch` ([josh sync](#synchronizing-a-josh-subtree)) -The [josh] tool is an alternative to git subtrees, which manages git history in a different way and scales better to larger repositories. Specific tooling is required to work with josh, you can check out the `miri` or `rust-analyzer` scripts linked above for inspiration. If you want to migrate a repository dependency from `git subtree` or `git submodule` to josh, you can check out [this guide](https://hackmd.io/7pOuxnkdQDaL1Y1FQr65xg). +### Josh subtrees -Below you can find a guide on how to perform push and pull synchronization with the main rustc repo using `git subtree`, although these instructions might differ repo from repo. +The [josh] tool is an alternative to git subtrees, which manages git history in a different way and scales better to larger repositories. Specific tooling is required to work with josh; you can check out the `miri` or `rust-analyzer` scripts linked above for inspiration. We provide a helper [`rustc-josh-sync`][josh-sync] tool to help with the synchronization, described [below](#synchronizing-a-josh-subtree). -### Synchronizing a subtree +### Synchronizing a Josh subtree + +We use a dedicated tool called [`rustc-josh-sync`][josh-sync] for performing Josh subtree updates. +Currently, we are migrating Josh repositories to it. So far, it is used in: + +- compiler-builtins +- rustc-dev-guide +- stdarch + +To install the tool: +``` +cargo install --locked --git https://github.com/rust-lang/josh-sync +``` + +Both pulls (synchronize changes from rust-lang/rust into the subtree) and pushes (synchronize +changes from the subtree to rust-lang/rust) are performed from the subtree repository (so first +switch to its repository checkout directory in your terminal). + +#### Performing pull +1) Checkout a new branch that will be used to create a PR into the subtree +2) Run the pull command + ``` + rustc-josh-sync pull + ``` +3) Push the branch to your fork and create a PR into the subtree repository + - If you have `gh` CLI installed, `rustc-josh-sync` can create the PR for you. + +#### Performing push + +1) Run the push command to create a branch named `<branch-name>` in a `rustc` fork under the `<gh-username>` account + ``` + rustc-josh-sync push <branch-name> <gh-username> + ``` +2) Create a PR from `<branch-name>` into `rust-lang/rust` + +### Creating a new Josh subtree dependency + +If you want to migrate a repository dependency from `git subtree` or `git submodule` to josh, you can check out [this guide](https://hackmd.io/7pOuxnkdQDaL1Y1FQr65xg). + +### Synchronizing a git subtree Periodically the changes made to subtree based dependencies need to be synchronized between this repository and the upstream tool repositories. @@ -129,3 +172,4 @@ the week leading up to the beta cut. [toolstate website]: https://rust-lang-nursery.github.io/rust-toolstate/ [Toolstate chapter]: https://forge.rust-lang.org/infra/toolstate.html [josh]: https://josh-project.github.io/josh/intro.html +[josh-sync]: https://github.com/rust-lang/josh-sync diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index 2536af09a23..1962314c70a 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -6,14 +6,14 @@ In the future, `std::offload` should become available in nightly builds for user First you need to clone and configure the Rust repository: ```bash -git clone --depth=1 git@github.com:rust-lang/rust.git +git clone git@github.com:rust-lang/rust cd rust ./configure --enable-llvm-link-shared --release-channel=nightly --enable-llvm-assertions --enable-offload --enable-enzyme --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs ``` Afterwards you can build rustc using: ```bash -./x.py build --stage 1 library +./x build --stage 1 library ``` Afterwards rustc toolchain link will allow you to use it through cargo: @@ -26,7 +26,7 @@ rustup toolchain install nightly # enables -Z unstable-options ## Build instruction for LLVM itself ```bash -git clone --depth=1 git@github.com:llvm/llvm-project.git +git clone git@github.com:llvm/llvm-project cd llvm-project mkdir build cd build @@ -40,7 +40,7 @@ This gives you a working LLVM build. ## Testing run ``` -./x.py test --stage 1 tests/codegen/gpu_offload +./x test --stage 1 tests/codegen/gpu_offload ``` ## Usage diff --git a/src/doc/rustc-dev-guide/src/solve/invariants.md b/src/doc/rustc-dev-guide/src/solve/invariants.md index fd12b195757..8ec15f339e5 100644 --- a/src/doc/rustc-dev-guide/src/solve/invariants.md +++ b/src/doc/rustc-dev-guide/src/solve/invariants.md @@ -4,17 +4,15 @@ FIXME: This file talks about invariants of the type system as a whole, not only There are a lot of invariants - things the type system guarantees to be true at all times - which are desirable or expected from other languages and type systems. Unfortunately, quite -a few of them do not hold in Rust right now. This is either a fundamental to its design or -caused by bugs and something that may change in the future. +a few of them do not hold in Rust right now. This is either fundamental to its design or +caused by bugs, and something that may change in the future. -It is important to know about the things you can assume while working on - and with - the +It is important to know about the things you can assume while working on, and with, the type system, so here's an incomplete and unofficial list of invariants of the core type system: -- ✅: this invariant mostly holds, with some weird exceptions, you can rely on it outside -of these cases -- ❌: this invariant does not hold, either due to bugs or by design, you must not rely on -it for soundness or have to be incredibly careful when doing so +- ✅: this invariant mostly holds, with some weird exceptions or current bugs +- ❌: this invariant does not hold, and is unlikely to do so in the future; do not rely on it for soundness or have to be incredibly careful when doing so ### `wf(X)` implies `wf(normalize(X))` ✅ @@ -23,20 +21,23 @@ well-formed after normalizing said aliases. We rely on this as otherwise we would have to re-check for well-formedness for these types. +This currently does not hold due to a type system unsoundness: [#84533](https://github.com/rust-lang/rust/issues/84533). + ### Structural equality modulo regions implies semantic equality ✅ If you have a some type and equate it to itself after replacing any regions with unique inference variables in both the lhs and rhs, the now potentially structurally different types should still be equal to each other. -Needed to prevent goals from succeeding in HIR typeck and then failing in MIR borrowck. -If this invariant is broken MIR typeck ends up failing with an ICE. +This is needed to prevent goals from succeeding in HIR typeck and then failing in MIR borrowck. +If this invariant is broken, MIR typeck ends up failing with an ICE. ### Applying inference results from a goal does not change its result ❌ TODO: this invariant is formulated in a weird way and needs to be elaborated. Pretty much: I would like this check to only fail if there's a solver bug: -https://github.com/rust-lang/rust/blob/2ffeb4636b4ae376f716dc4378a7efb37632dc2d/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs#L391-L407 +<https://github.com/rust-lang/rust/blob/2ffeb4636b4ae376f716dc4378a7efb37632dc2d/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs#L391-L407>. +We should readd this check and see where it breaks :3 If we prove some goal/equate types/whatever, apply the resulting inference constraints, and then redo the original action, the result should be the same. @@ -73,82 +74,99 @@ Many of the currently known unsound issues end up relying on this invariant bein It is however very difficult to imagine a sound type system without this invariant, so the issue is that the invariant is broken, not that we incorrectly rely on it. -### Generic goals and their instantiations have the same result ✅ +### The type system is complete ❌ + +The type system is not complete. +It often adds unnecessary inference constraints, and errors even though the goal could hold. + +- method selection +- opaque type inference +- handling type outlives constraints +- preferring `ParamEnv` candidates over `Impl` candidates during candidate selection +in the trait solver + +### Goals keep their result from HIR typeck afterwards ✅ + +Having a goal which succeeds during HIR typeck but fails when being reevaluated during MIR borrowck causes ICE, e.g. [#140211](https://github.com/rust-lang/rust/issues/140211). -Pretty much: If we successfully typecheck a generic function concrete instantiations -of that function should also typeck. We should not get errors post-monomorphization. -We can however get overflow errors at that point. +Having a goal which succeeds during HIR typeck but fails after being instantiated is unsound, e.g. [#140212](https://github.com/rust-lang/rust/issues/140212). -TODO: example for overflow error post-monomorphization +It is interesting that we allow some incompleteness in the trait solver while still maintaining this limitation. It would be nice if there was a clear way to separate the "allowed incompleteness" from behavior which would break this invariant. + +#### Normalization must not change results This invariant is relied on to allow the normalization of generic aliases. Breaking -it can easily result in unsoundness, e.g. [#57893](https://github.com/rust-lang/rust/issues/57893) +it can easily result in unsoundness, e.g. [#57893](https://github.com/rust-lang/rust/issues/57893). + +#### Goals may still overflow after instantiation + +This happens they start to hit the recursion limit. +We also have diverging aliases which are scuffed. +It's unclear how these should be handled :3 ### Trait goals in empty environments are proven by a unique impl ✅ If a trait goal holds with an empty environment, there should be a unique `impl`, either user-defined or builtin, which is used to prove that goal. This is -necessary to select a unique method. +necessary to select unique methods and associated items. -We do however break this invariant in few cases, some of which are due to bugs, -some by design: +We do however break this invariant in a few cases, some of which are due to bugs, some by design: - *marker traits* are allowed to overlap as they do not have associated items - *specialization* allows specializing impls to overlap with their parent - the builtin trait object trait implementation can overlap with a user-defined impl: -[#57893] +[#57893](https://github.com/rust-lang/rust/issues/57893) -### The type system is complete ❌ - -The type system is not complete, it often adds unnecessary inference constraints, and errors -even though the goal could hold. - -- method selection -- opaque type inference -- handling type outlives constraints -- preferring `ParamEnv` candidates over `Impl` candidates during candidate selection -in the trait solver #### The type system is complete during the implicit negative overlap check in coherence ✅ -For more on overlap checking: [coherence] +For more on overlap checking, see [Coherence chapter]. -During the implicit negative overlap check in coherence we must never return *error* for -goals which can be proven. This would allow for overlapping impls with potentially different -associated items, breaking a bunch of other invariants. +During the implicit negative overlap check in coherence, +we must never return *error* for goals which can be proven. +This would allow for overlapping impls with potentially different associated items, +breaking a bunch of other invariants. This invariant is currently broken in many different ways while actually something we rely on. We have to be careful as it is quite easy to break: - generalization of aliases - generalization during subtyping binders (luckily not exploitable in coherence) -### Trait solving must be (free) lifetime agnostic ✅ +### Trait solving must not depend on lifetimes being different ✅ + +If a goal holds with lifetimes being different, it must also hold with these lifetimes being the same. We otherwise get post-monomorphization errors during codegen or unsoundness due to invalid vtables. -Trait solving during codegen should have the same result as during typeck. As we erase -all free regions during codegen we must not rely on them during typeck. A noteworthy example -is special behavior for `'static`. +We could also just get inconsistent behavior when first proving a goal with different lifetimes which are later constrained to be equal. + +### Trait solving in bodies must not depend on lifetimes being equal ✅ We also have to be careful with relying on equality of regions in the trait solver. This is fine for codegen, as we treat all erased regions as equal. We can however lose equality information from HIR to MIR typeck. -The new solver "uniquifies regions" during canonicalization, canonicalizing `u32: Trait<'x, 'x>` -as `exists<'0, '1> u32: Trait<'0, '1>`, to make it harder to rely on this property. +This currently does not hold with the new solver: [trait-system-refactor-initiative#27](https://github.com/rust-lang/trait-system-refactor-initiative/issues/27). ### Removing ambiguity makes strictly more things compile ❌ Ideally we *should* not rely on ambiguity for things to compile. Not doing that will cause future improvements to be breaking changes. -Due to *incompleteness* this is not the case and improving inference can result in inference -changes, breaking existing projects. +Due to *incompleteness* this is not the case, +and improving inference can result in inference changes, breaking existing projects. ### Semantic equality implies structural equality ✅ Two types being equal in the type system must mean that they have the same `TypeId` after instantiating their generic parameters with concrete -arguments. This currently does not hold: [#97156]. +arguments. We can otherwise use their different `TypeId`s to impact trait selection. + +We lookup types using structural equality during codegen, but this shouldn't necessarily be unsound +- may result in redundant method codegen or backend type check errors? +- we also rely on it in CTFE assertions + +### Semantically different types have different `TypeId`s ✅ + +Semantically different `'static` types need different `TypeId`s to avoid transmutes, +for example `for<'a> fn(&'a str)` vs `fn(&'static str)` must have a different `TypeId`. + -[#57893]: https://github.com/rust-lang/rust/issues/57893 -[#97156]: https://github.com/rust-lang/rust/issues/97156 -[#114936]: https://github.com/rust-lang/rust/issues/114936 -[coherence]: ../coherence.md +[coherence chapter]: ../coherence.md diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index 839076b809d..63aa08c389c 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -59,7 +59,7 @@ not be exhaustive. Directives can generally be found by browsing the | `aux-crate` | Like `aux-build` but makes available as extern prelude | All except `run-make` | `<extern_prelude_name>=<path/to/aux/file.rs>` | | `aux-codegen-backend` | Similar to `aux-build` but pass the compiled dylib to `-Zcodegen-backend` when building the main file | `ui-fulldeps` | Path to codegen backend file | | `proc-macro` | Similar to `aux-build`, but for aux forces host and don't use `-Cprefer-dynamic`[^pm]. | All except `run-make` | Path to auxiliary proc-macro `.rs` file | -| `build-aux-docs` | Build docs for auxiliaries as well | All except `run-make` | N/A | +| `build-aux-docs` | Build docs for auxiliaries as well. Note that this only works with `aux-build`, not `aux-crate`. | All except `run-make` | N/A | [^pm]: please see the Auxiliary proc-macro section in the [compiletest](./compiletest.md) chapter for specifics. diff --git a/src/doc/rustc-dev-guide/src/tests/intro.md b/src/doc/rustc-dev-guide/src/tests/intro.md index c55d60f4a5c..79b96c450a8 100644 --- a/src/doc/rustc-dev-guide/src/tests/intro.md +++ b/src/doc/rustc-dev-guide/src/tests/intro.md @@ -111,12 +111,14 @@ and it can be invoked so: This requires building all of the documentation, which might take a while. -### Dist check +### `distcheck` `distcheck` verifies that the source distribution tarball created by the build system will unpack, build, and run all tests. -> Example: `./x test distcheck` +```console +./x test distcheck +``` ### Tool tests diff --git a/src/doc/rustc-dev-guide/src/tests/misc.md b/src/doc/rustc-dev-guide/src/tests/misc.md index c0288b3dd10..39f88174879 100644 --- a/src/doc/rustc-dev-guide/src/tests/misc.md +++ b/src/doc/rustc-dev-guide/src/tests/misc.md @@ -9,7 +9,7 @@ for testing: - `RUSTC_BOOTSTRAP=1` will "cheat" and bypass usual stability checking, allowing you to use unstable features and cli flags on a stable `rustc`. -- `RUSTC_BOOTSTRAP=-1` will force a given `rustc` to pretend that is a stable +- `RUSTC_BOOTSTRAP=-1` will force a given `rustc` to pretend it is a stable compiler, even if it's actually a nightly `rustc`. This is useful because some behaviors of the compiler (e.g. diagnostics) can differ depending on whether the compiler is nightly or not. diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 9c077e5b7b6..f309e34c75b 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1086,7 +1086,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &MiriInterpCx<'tcx>, instance: ty::Instance<'tcx>, ) -> InterpResult<'tcx> { - let attrs = ecx.tcx.codegen_fn_attrs(instance.def_id()); + let attrs = ecx.tcx.codegen_instance_attrs(instance.def); if attrs .target_features .iter() @@ -1790,7 +1790,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx.tcx.sess.opts.unstable_opts.cross_crate_inline_threshold, InliningThreshold::Always ) || !matches!( - ecx.tcx.codegen_fn_attrs(instance.def_id()).inline, + ecx.tcx.codegen_instance_attrs(instance.def).inline, InlineAttr::Never ); !is_generic && !can_be_inlined diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 7432a82080d..e55cd80943d 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -154,6 +154,22 @@ dependencies = [ ] [[package]] +name = "cargo-util-schemas" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror 2.0.12", + "toml", + "unicode-xid", + "url", +] + +[[package]] name = "cargo_metadata" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -161,7 +177,22 @@ checksum = "4f7835cfc6135093070e95eb2b53e5d9b5c403dc3a6be6040ee026270aa82502" dependencies = [ "camino", "cargo-platform", - "cargo-util-schemas", + "cargo-util-schemas 0.2.0", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "cargo_metadata" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868" +dependencies = [ + "camino", + "cargo-platform", + "cargo-util-schemas 0.8.2", "semver", "serde", "serde_json", @@ -1190,13 +1221,16 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" name = "lsp-server" version = "0.7.8" dependencies = [ + "anyhow", "crossbeam-channel", "ctrlc", "log", "lsp-types", + "rustc-hash 2.1.1", "serde", "serde_derive", "serde_json", + "toolchain", ] [[package]] @@ -1471,7 +1505,7 @@ dependencies = [ "edition", "expect-test", "ra-ap-rustc_lexer", - "rustc-literal-escaper 0.0.4", + "rustc-literal-escaper", "stdx", "tracing", ] @@ -1599,7 +1633,7 @@ dependencies = [ name = "proc-macro-test" version = "0.0.0" dependencies = [ - "cargo_metadata", + "cargo_metadata 0.20.0", ] [[package]] @@ -1640,7 +1674,7 @@ version = "0.0.0" dependencies = [ "anyhow", "base-db", - "cargo_metadata", + "cargo_metadata 0.21.0", "cfg", "expect-test", "intern", @@ -1722,9 +1756,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_abi" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a967e3a9cd3e38b543f503978e0eccee461e3aea3f7b10e944959bff41dbe612" +checksum = "3ee51482d1c9d3e538acda8cce723db8eea1a81540544bf362bf4c3d841b2329" dependencies = [ "bitflags 2.9.1", "ra-ap-rustc_hashes", @@ -1734,18 +1768,18 @@ dependencies = [ [[package]] name = "ra-ap-rustc_hashes" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea4c755ecbbffa5743c251344f484ebe571ec7bc5b36d80b2a8ae775d1a7a40" +checksum = "19c8f1e0c28e24e1b4c55dc08058c6c9829df2204497d4034259f491d348c204" dependencies = [ "rustc-stable-hash", ] [[package]] name = "ra-ap-rustc_index" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aca7ad7cf911538c619caa2162339fe98637e9e46f11bb0484ef96735df4d64a" +checksum = "5f33f429cec6b92fa2c7243883279fb29dd233fdc3e94099aff32aa91aa87f50" dependencies = [ "ra-ap-rustc_index_macros", "smallvec", @@ -1753,9 +1787,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_index_macros" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8767ba551c9355bc3031be072cc4bb0381106e5e7cd275e72b7a8c76051c4070" +checksum = "b9b55910dbe1fe7ef34bdc1d1bcb41e99b377eb680ea58a1218d95d6b4152257" dependencies = [ "proc-macro2", "quote", @@ -1764,9 +1798,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_lexer" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6101374afb267e6c27e4e2eb0b1352e9f3504c1a8f716f619cd39244e2ed92ab" +checksum = "22944e31fb91e9b3e75bcbc91e37d958b8c0825a6160927f2856831d2ce83b36" dependencies = [ "memchr", "unicode-properties", @@ -1775,19 +1809,19 @@ dependencies = [ [[package]] name = "ra-ap-rustc_parse_format" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd88a19f00da4f43e6727d5013444cbc399804b5046dfa2bbcd28ebed3970ce" +checksum = "81057891bc2063ad9e353f29462fbc47a0f5072560af34428ae9313aaa5e9d97" dependencies = [ "ra-ap-rustc_lexer", - "rustc-literal-escaper 0.0.2", + "rustc-literal-escaper", ] [[package]] name = "ra-ap-rustc_pattern_analysis" -version = "0.116.0" +version = "0.121.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb332dd32d7850a799862533b1c021e6062558861a4ad57817bf522499fbb892" +checksum = "fe21a3542980d56d2435e96c2720773cac1c63fd4db666417e414729da192eb3" dependencies = [ "ra-ap-rustc_index", "rustc-hash 2.1.1", @@ -1855,7 +1889,7 @@ version = "0.0.0" dependencies = [ "anyhow", "base64", - "cargo_metadata", + "cargo_metadata 0.21.0", "cfg", "crossbeam-channel", "dirs", @@ -1934,12 +1968,6 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-literal-escaper" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0041b6238913c41fe704213a4a9329e2f685a156d1781998128b4149c230ad04" - -[[package]] -name = "rustc-literal-escaper" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab03008eb631b703dd16978282ae36c73282e7922fe101a4bd072a40ecea7b8b" @@ -2231,7 +2259,7 @@ dependencies = [ "rayon", "rowan", "rustc-hash 2.1.1", - "rustc-literal-escaper 0.0.4", + "rustc-literal-escaper", "rustc_apfloat", "smol_str", "stdx", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index d268ce5b0bb..41fa06a76a7 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -4,7 +4,7 @@ exclude = ["crates/proc-macro-srv/proc-macro-test/imp"] resolver = "2" [workspace.package] -rust-version = "1.86" +rust-version = "1.88" edition = "2024" license = "MIT OR Apache-2.0" authors = ["rust-analyzer team"] @@ -89,11 +89,11 @@ vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } edition = { path = "./crates/edition", version = "0.0.0" } -ra-ap-rustc_lexer = { version = "0.116", default-features = false } -ra-ap-rustc_parse_format = { version = "0.116", default-features = false } -ra-ap-rustc_index = { version = "0.116", default-features = false } -ra-ap-rustc_abi = { version = "0.116", default-features = false } -ra-ap-rustc_pattern_analysis = { version = "0.116", default-features = false } +ra-ap-rustc_lexer = { version = "0.121", default-features = false } +ra-ap-rustc_parse_format = { version = "0.121", default-features = false } +ra-ap-rustc_index = { version = "0.121", default-features = false } +ra-ap-rustc_abi = { version = "0.121", default-features = false } +ra-ap-rustc_pattern_analysis = { version = "0.121", default-features = false } # local crates that aren't published to crates.io. These should not have versions. @@ -106,7 +106,7 @@ lsp-server = { version = "0.7.8" } anyhow = "1.0.98" arrayvec = "0.7.6" bitflags = "2.9.1" -cargo_metadata = "0.20.0" +cargo_metadata = "0.21.0" camino = "1.1.10" chalk-solve = { version = "0.103.0", default-features = false } chalk-ir = "0.103.0" @@ -138,7 +138,11 @@ rayon = "1.10.0" rowan = "=0.15.15" # Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work # on impls without it -salsa = { version = "0.23.0", default-features = true, features = ["rayon","salsa_unstable", "macros"] } +salsa = { version = "0.23.0", default-features = true, features = [ + "rayon", + "salsa_unstable", + "macros", +] } salsa-macros = "0.23.0" semver = "1.0.26" serde = { version = "1.0.219" } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs index 51612f341a1..d3dfc05eb29 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs @@ -22,6 +22,7 @@ use rustc_hash::FxHashMap; use smallvec::SmallVec; use span::{Edition, SyntaxContext}; use syntax::{AstPtr, SyntaxNodePtr, ast}; +use thin_vec::ThinVec; use triomphe::Arc; use tt::TextRange; @@ -93,17 +94,17 @@ pub type TypeSource = InFile<TypePtr>; pub type LifetimePtr = AstPtr<ast::Lifetime>; pub type LifetimeSource = InFile<LifetimePtr>; +// We split the store into types-only and expressions, because most stores (e.g. generics) +// don't store any expressions and this saves memory. Same thing for the source map. #[derive(Debug, PartialEq, Eq)] -pub struct ExpressionStore { - pub exprs: Arena<Expr>, - pub pats: Arena<Pat>, - pub bindings: Arena<Binding>, - pub labels: Arena<Label>, - pub types: Arena<TypeRef>, - pub lifetimes: Arena<LifetimeRef>, +struct ExpressionOnlyStore { + exprs: Arena<Expr>, + pats: Arena<Pat>, + bindings: Arena<Binding>, + labels: Arena<Label>, /// Id of the closure/coroutine that owns the corresponding binding. If a binding is owned by the /// top level expression, it will not be listed in here. - pub binding_owners: FxHashMap<BindingId, ExprId>, + binding_owners: FxHashMap<BindingId, ExprId>, /// Block expressions in this store that may contain inner items. block_scopes: Box<[BlockId]>, @@ -114,8 +115,15 @@ pub struct ExpressionStore { ident_hygiene: FxHashMap<ExprOrPatId, HygieneId>, } +#[derive(Debug, PartialEq, Eq)] +pub struct ExpressionStore { + expr_only: Option<Box<ExpressionOnlyStore>>, + pub types: Arena<TypeRef>, + pub lifetimes: Arena<LifetimeRef>, +} + #[derive(Debug, Eq, Default)] -pub struct ExpressionStoreSourceMap { +struct ExpressionOnlySourceMap { // AST expressions can create patterns in destructuring assignments. Therefore, `ExprSource` can also map // to `PatId`, and `PatId` can also map to `ExprSource` (the other way around is unaffected). expr_map: FxHashMap<ExprSource, ExprOrPatId>, @@ -127,12 +135,6 @@ pub struct ExpressionStoreSourceMap { label_map: FxHashMap<LabelSource, LabelId>, label_map_back: ArenaMap<LabelId, LabelSource>, - types_map_back: ArenaMap<TypeRefId, TypeSource>, - types_map: FxHashMap<TypeSource, TypeRefId>, - - lifetime_map_back: ArenaMap<LifetimeRefId, LifetimeSource>, - lifetime_map: FxHashMap<LifetimeSource, LifetimeRefId>, - binding_definitions: ArenaMap<BindingId, SmallVec<[PatId; 2 * size_of::<usize>() / size_of::<PatId>()]>>, @@ -143,14 +145,17 @@ pub struct ExpressionStoreSourceMap { template_map: Option<Box<FormatTemplate>>, - pub expansions: FxHashMap<InFile<MacroCallPtr>, MacroCallId>, + expansions: FxHashMap<InFile<MacroCallPtr>, MacroCallId>, /// Diagnostics accumulated during lowering. These contain `AstPtr`s and so are stored in /// the source map (since they're just as volatile). - pub diagnostics: Vec<ExpressionStoreDiagnostics>, + // + // We store diagnostics on the `ExpressionOnlySourceMap` because diagnostics are rare (except + // maybe for cfgs, and they are also not common in type places). + diagnostics: ThinVec<ExpressionStoreDiagnostics>, } -impl PartialEq for ExpressionStoreSourceMap { +impl PartialEq for ExpressionOnlySourceMap { fn eq(&self, other: &Self) -> bool { // we only need to compare one of the two mappings // as the other is a reverse mapping and thus will compare @@ -162,10 +167,6 @@ impl PartialEq for ExpressionStoreSourceMap { pat_map_back, label_map: _, label_map_back, - types_map_back, - types_map: _, - lifetime_map_back, - lifetime_map: _, // If this changed, our pattern data must have changed binding_definitions: _, // If this changed, our expression data must have changed @@ -179,14 +180,40 @@ impl PartialEq for ExpressionStoreSourceMap { *expr_map_back == other.expr_map_back && *pat_map_back == other.pat_map_back && *label_map_back == other.label_map_back - && *types_map_back == other.types_map_back - && *lifetime_map_back == other.lifetime_map_back && *template_map == other.template_map && *expansions == other.expansions && *diagnostics == other.diagnostics } } +#[derive(Debug, Eq, Default)] +pub struct ExpressionStoreSourceMap { + expr_only: Option<Box<ExpressionOnlySourceMap>>, + + types_map_back: ArenaMap<TypeRefId, TypeSource>, + types_map: FxHashMap<TypeSource, TypeRefId>, + + lifetime_map_back: ArenaMap<LifetimeRefId, LifetimeSource>, + #[expect( + unused, + reason = "this is here for completeness, and maybe we'll need it in the future" + )] + lifetime_map: FxHashMap<LifetimeSource, LifetimeRefId>, +} + +impl PartialEq for ExpressionStoreSourceMap { + fn eq(&self, other: &Self) -> bool { + // we only need to compare one of the two mappings + // as the other is a reverse mapping and thus will compare + // the same as normal mapping + let Self { expr_only, types_map_back, types_map: _, lifetime_map_back, lifetime_map: _ } = + self; + *expr_only == other.expr_only + && *types_map_back == other.types_map_back + && *lifetime_map_back == other.lifetime_map_back + } +} + /// The body of an item (function, const etc.). #[derive(Debug, Eq, PartialEq, Default)] pub struct ExpressionStoreBuilder { @@ -199,6 +226,42 @@ pub struct ExpressionStoreBuilder { pub types: Arena<TypeRef>, block_scopes: Vec<BlockId>, ident_hygiene: FxHashMap<ExprOrPatId, HygieneId>, + + // AST expressions can create patterns in destructuring assignments. Therefore, `ExprSource` can also map + // to `PatId`, and `PatId` can also map to `ExprSource` (the other way around is unaffected). + expr_map: FxHashMap<ExprSource, ExprOrPatId>, + expr_map_back: ArenaMap<ExprId, ExprOrPatSource>, + + pat_map: FxHashMap<PatSource, ExprOrPatId>, + pat_map_back: ArenaMap<PatId, ExprOrPatSource>, + + label_map: FxHashMap<LabelSource, LabelId>, + label_map_back: ArenaMap<LabelId, LabelSource>, + + types_map_back: ArenaMap<TypeRefId, TypeSource>, + types_map: FxHashMap<TypeSource, TypeRefId>, + + lifetime_map_back: ArenaMap<LifetimeRefId, LifetimeSource>, + lifetime_map: FxHashMap<LifetimeSource, LifetimeRefId>, + + binding_definitions: + ArenaMap<BindingId, SmallVec<[PatId; 2 * size_of::<usize>() / size_of::<PatId>()]>>, + + /// We don't create explicit nodes for record fields (`S { record_field: 92 }`). + /// Instead, we use id of expression (`92`) to identify the field. + field_map_back: FxHashMap<ExprId, FieldSource>, + pat_field_map_back: FxHashMap<PatId, PatFieldSource>, + + template_map: Option<Box<FormatTemplate>>, + + expansions: FxHashMap<InFile<MacroCallPtr>, MacroCallId>, + + /// Diagnostics accumulated during lowering. These contain `AstPtr`s and so are stored in + /// the source map (since they're just as volatile). + // + // We store diagnostics on the `ExpressionOnlySourceMap` because diagnostics are rare (except + // maybe for cfgs, and they are also not common in type places). + pub(crate) diagnostics: Vec<ExpressionStoreDiagnostics>, } #[derive(Default, Debug, Eq, PartialEq)] @@ -226,7 +289,7 @@ pub enum ExpressionStoreDiagnostics { } impl ExpressionStoreBuilder { - pub fn finish(self) -> ExpressionStore { + pub fn finish(self) -> (ExpressionStore, ExpressionStoreSourceMap) { let Self { block_scopes, mut exprs, @@ -237,6 +300,23 @@ impl ExpressionStoreBuilder { mut ident_hygiene, mut types, mut lifetimes, + + mut expr_map, + mut expr_map_back, + mut pat_map, + mut pat_map_back, + mut label_map, + mut label_map_back, + mut types_map_back, + mut types_map, + mut lifetime_map_back, + mut lifetime_map, + mut binding_definitions, + mut field_map_back, + mut pat_field_map_back, + mut template_map, + mut expansions, + diagnostics, } = self; exprs.shrink_to_fit(); labels.shrink_to_fit(); @@ -247,24 +327,90 @@ impl ExpressionStoreBuilder { types.shrink_to_fit(); lifetimes.shrink_to_fit(); - ExpressionStore { - exprs, - pats, - bindings, - labels, - binding_owners, - types, - lifetimes, - block_scopes: block_scopes.into_boxed_slice(), - ident_hygiene, + expr_map.shrink_to_fit(); + expr_map_back.shrink_to_fit(); + pat_map.shrink_to_fit(); + pat_map_back.shrink_to_fit(); + label_map.shrink_to_fit(); + label_map_back.shrink_to_fit(); + types_map_back.shrink_to_fit(); + types_map.shrink_to_fit(); + lifetime_map_back.shrink_to_fit(); + lifetime_map.shrink_to_fit(); + binding_definitions.shrink_to_fit(); + field_map_back.shrink_to_fit(); + pat_field_map_back.shrink_to_fit(); + if let Some(template_map) = &mut template_map { + let FormatTemplate { + format_args_to_captures, + asm_to_captures, + implicit_capture_to_source, + } = &mut **template_map; + format_args_to_captures.shrink_to_fit(); + asm_to_captures.shrink_to_fit(); + implicit_capture_to_source.shrink_to_fit(); } + expansions.shrink_to_fit(); + + let has_exprs = + !exprs.is_empty() || !labels.is_empty() || !pats.is_empty() || !bindings.is_empty(); + + let store = { + let expr_only = if has_exprs { + Some(Box::new(ExpressionOnlyStore { + exprs, + pats, + bindings, + labels, + binding_owners, + block_scopes: block_scopes.into_boxed_slice(), + ident_hygiene, + })) + } else { + None + }; + ExpressionStore { expr_only, types, lifetimes } + }; + + let source_map = { + let expr_only = if has_exprs || !expansions.is_empty() || !diagnostics.is_empty() { + Some(Box::new(ExpressionOnlySourceMap { + expr_map, + expr_map_back, + pat_map, + pat_map_back, + label_map, + label_map_back, + binding_definitions, + field_map_back, + pat_field_map_back, + template_map, + expansions, + diagnostics: ThinVec::from_iter(diagnostics), + })) + } else { + None + }; + ExpressionStoreSourceMap { + expr_only, + types_map_back, + types_map, + lifetime_map_back, + lifetime_map, + } + }; + + (store, source_map) } } impl ExpressionStore { - pub fn empty_singleton() -> Arc<Self> { - static EMPTY: LazyLock<Arc<ExpressionStore>> = - LazyLock::new(|| Arc::new(ExpressionStoreBuilder::default().finish())); + pub fn empty_singleton() -> (Arc<ExpressionStore>, Arc<ExpressionStoreSourceMap>) { + static EMPTY: LazyLock<(Arc<ExpressionStore>, Arc<ExpressionStoreSourceMap>)> = + LazyLock::new(|| { + let (store, source_map) = ExpressionStoreBuilder::default().finish(); + (Arc::new(store), Arc::new(source_map)) + }); EMPTY.clone() } @@ -273,7 +419,12 @@ impl ExpressionStore { &'a self, db: &'a dyn DefDatabase, ) -> impl Iterator<Item = (BlockId, &'a DefMap)> + 'a { - self.block_scopes.iter().map(move |&block| (block, block_def_map(db, block))) + self.expr_only + .as_ref() + .map(|it| &*it.block_scopes) + .unwrap_or_default() + .iter() + .map(move |&block| (block, block_def_map(db, block))) } pub fn walk_bindings_in_pat(&self, pat_id: PatId, mut f: impl FnMut(BindingId)) { @@ -320,7 +471,8 @@ impl ExpressionStore { } pub fn is_binding_upvar(&self, binding: BindingId, relative_to: ExprId) -> bool { - match self.binding_owners.get(&binding) { + let Some(expr_only) = &self.expr_only else { return false }; + match expr_only.binding_owners.get(&binding) { Some(it) => { // We assign expression ids in a way that outer closures will receive // a lower id @@ -330,6 +482,11 @@ impl ExpressionStore { } } + #[inline] + pub fn binding_owner(&self, id: BindingId) -> Option<ExprId> { + self.expr_only.as_ref()?.binding_owners.get(&id).copied() + } + /// Walks the immediate children expressions and calls `f` for each child expression. /// /// Note that this does not walk const blocks. @@ -601,16 +758,22 @@ impl ExpressionStore { }); } + #[inline] + #[track_caller] + fn assert_expr_only(&self) -> &ExpressionOnlyStore { + self.expr_only.as_ref().expect("should have `ExpressionStore::expr_only`") + } + fn binding_hygiene(&self, binding: BindingId) -> HygieneId { - self.bindings[binding].hygiene + self.assert_expr_only().bindings[binding].hygiene } pub fn expr_path_hygiene(&self, expr: ExprId) -> HygieneId { - self.ident_hygiene.get(&expr.into()).copied().unwrap_or(HygieneId::ROOT) + self.assert_expr_only().ident_hygiene.get(&expr.into()).copied().unwrap_or(HygieneId::ROOT) } pub fn pat_path_hygiene(&self, pat: PatId) -> HygieneId { - self.ident_hygiene.get(&pat.into()).copied().unwrap_or(HygieneId::ROOT) + self.assert_expr_only().ident_hygiene.get(&pat.into()).copied().unwrap_or(HygieneId::ROOT) } pub fn expr_or_pat_path_hygiene(&self, id: ExprOrPatId) -> HygieneId { @@ -619,43 +782,72 @@ impl ExpressionStore { ExprOrPatId::PatId(id) => self.pat_path_hygiene(id), } } + + #[inline] + pub fn exprs(&self) -> impl Iterator<Item = (ExprId, &Expr)> { + match &self.expr_only { + Some(it) => it.exprs.iter(), + None => const { &Arena::new() }.iter(), + } + } + + #[inline] + pub fn pats(&self) -> impl Iterator<Item = (PatId, &Pat)> { + match &self.expr_only { + Some(it) => it.pats.iter(), + None => const { &Arena::new() }.iter(), + } + } + + #[inline] + pub fn bindings(&self) -> impl Iterator<Item = (BindingId, &Binding)> { + match &self.expr_only { + Some(it) => it.bindings.iter(), + None => const { &Arena::new() }.iter(), + } + } } impl Index<ExprId> for ExpressionStore { type Output = Expr; + #[inline] fn index(&self, expr: ExprId) -> &Expr { - &self.exprs[expr] + &self.assert_expr_only().exprs[expr] } } impl Index<PatId> for ExpressionStore { type Output = Pat; + #[inline] fn index(&self, pat: PatId) -> &Pat { - &self.pats[pat] + &self.assert_expr_only().pats[pat] } } impl Index<LabelId> for ExpressionStore { type Output = Label; + #[inline] fn index(&self, label: LabelId) -> &Label { - &self.labels[label] + &self.assert_expr_only().labels[label] } } impl Index<BindingId> for ExpressionStore { type Output = Binding; + #[inline] fn index(&self, b: BindingId) -> &Binding { - &self.bindings[b] + &self.assert_expr_only().bindings[b] } } impl Index<TypeRefId> for ExpressionStore { type Output = TypeRef; + #[inline] fn index(&self, b: TypeRefId) -> &TypeRef { &self.types[b] } @@ -664,6 +856,7 @@ impl Index<TypeRefId> for ExpressionStore { impl Index<LifetimeRefId> for ExpressionStore { type Output = LifetimeRef; + #[inline] fn index(&self, b: LifetimeRefId) -> &LifetimeRef { &self.lifetimes[b] } @@ -684,12 +877,6 @@ impl Index<PathId> for ExpressionStore { // FIXME: Change `node_` prefix to something more reasonable. // Perhaps `expr_syntax` and `expr_id`? impl ExpressionStoreSourceMap { - pub fn empty_singleton() -> Arc<Self> { - static EMPTY: LazyLock<Arc<ExpressionStoreSourceMap>> = - LazyLock::new(|| Arc::new(ExpressionStoreSourceMap::default())); - EMPTY.clone() - } - pub fn expr_or_pat_syntax(&self, id: ExprOrPatId) -> Result<ExprOrPatSource, SyntheticSyntax> { match id { ExprOrPatId::ExprId(id) => self.expr_syntax(id), @@ -697,30 +884,46 @@ impl ExpressionStoreSourceMap { } } + #[inline] + fn expr_or_synthetic(&self) -> Result<&ExpressionOnlySourceMap, SyntheticSyntax> { + self.expr_only.as_deref().ok_or(SyntheticSyntax) + } + + #[inline] + fn expr_only(&self) -> Option<&ExpressionOnlySourceMap> { + self.expr_only.as_deref() + } + + #[inline] + #[track_caller] + fn assert_expr_only(&self) -> &ExpressionOnlySourceMap { + self.expr_only.as_ref().expect("should have `ExpressionStoreSourceMap::expr_only`") + } + pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprOrPatSource, SyntheticSyntax> { - self.expr_map_back.get(expr).cloned().ok_or(SyntheticSyntax) + self.expr_or_synthetic()?.expr_map_back.get(expr).cloned().ok_or(SyntheticSyntax) } pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprOrPatId> { let src = node.map(AstPtr::new); - self.expr_map.get(&src).cloned() + self.expr_only()?.expr_map.get(&src).cloned() } pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<MacroCallId> { let src = node.map(AstPtr::new); - self.expansions.get(&src).cloned() + self.expr_only()?.expansions.get(&src).cloned() } pub fn macro_calls(&self) -> impl Iterator<Item = (InFile<MacroCallPtr>, MacroCallId)> + '_ { - self.expansions.iter().map(|(&a, &b)| (a, b)) + self.expr_only().into_iter().flat_map(|it| it.expansions.iter().map(|(&a, &b)| (a, b))) } pub fn pat_syntax(&self, pat: PatId) -> Result<ExprOrPatSource, SyntheticSyntax> { - self.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax) + self.expr_or_synthetic()?.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax) } pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<ExprOrPatId> { - self.pat_map.get(&node.map(AstPtr::new)).cloned() + self.expr_only()?.pat_map.get(&node.map(AstPtr::new)).cloned() } pub fn type_syntax(&self, id: TypeRefId) -> Result<TypeSource, SyntheticSyntax> { @@ -732,49 +935,50 @@ impl ExpressionStoreSourceMap { } pub fn label_syntax(&self, label: LabelId) -> LabelSource { - self.label_map_back[label] + self.assert_expr_only().label_map_back[label] } pub fn patterns_for_binding(&self, binding: BindingId) -> &[PatId] { - self.binding_definitions.get(binding).map_or(&[], Deref::deref) + self.assert_expr_only().binding_definitions.get(binding).map_or(&[], Deref::deref) } pub fn node_label(&self, node: InFile<&ast::Label>) -> Option<LabelId> { let src = node.map(AstPtr::new); - self.label_map.get(&src).cloned() + self.expr_only()?.label_map.get(&src).cloned() } pub fn field_syntax(&self, expr: ExprId) -> FieldSource { - self.field_map_back[&expr] + self.assert_expr_only().field_map_back[&expr] } pub fn pat_field_syntax(&self, pat: PatId) -> PatFieldSource { - self.pat_field_map_back[&pat] + self.assert_expr_only().pat_field_map_back[&pat] } pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprOrPatId> { let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::MacroExpr>).map(AstPtr::upcast); - self.expr_map.get(&src).copied() + self.expr_only()?.expr_map.get(&src).copied() } pub fn expansions(&self) -> impl Iterator<Item = (&InFile<MacroCallPtr>, &MacroCallId)> { - self.expansions.iter() + self.expr_only().into_iter().flat_map(|it| it.expansions.iter()) } pub fn expansion(&self, node: InFile<&ast::MacroCall>) -> Option<MacroCallId> { - self.expansions.get(&node.map(AstPtr::new)).copied() + self.expr_only()?.expansions.get(&node.map(AstPtr::new)).copied() } pub fn implicit_format_args( &self, node: InFile<&ast::FormatArgsExpr>, ) -> Option<(HygieneId, &[(syntax::TextRange, Name)])> { + let expr_only = self.expr_only()?; let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>); - let (hygiene, names) = self + let (hygiene, names) = expr_only .template_map .as_ref()? .format_args_to_captures - .get(&self.expr_map.get(&src)?.as_expr()?)?; + .get(&expr_only.expr_map.get(&src)?.as_expr()?)?; Some((*hygiene, &**names)) } @@ -782,67 +986,28 @@ impl ExpressionStoreSourceMap { &self, capture_expr: ExprId, ) -> Option<InFile<(ExprPtr, TextRange)>> { - self.template_map.as_ref()?.implicit_capture_to_source.get(&capture_expr).copied() + self.expr_only()? + .template_map + .as_ref()? + .implicit_capture_to_source + .get(&capture_expr) + .copied() } pub fn asm_template_args( &self, node: InFile<&ast::AsmExpr>, ) -> Option<(ExprId, &[Vec<(syntax::TextRange, usize)>])> { + let expr_only = self.expr_only()?; let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>); - let expr = self.expr_map.get(&src)?.as_expr()?; - Some(expr) - .zip(self.template_map.as_ref()?.asm_to_captures.get(&expr).map(std::ops::Deref::deref)) + let expr = expr_only.expr_map.get(&src)?.as_expr()?; + Some(expr).zip( + expr_only.template_map.as_ref()?.asm_to_captures.get(&expr).map(std::ops::Deref::deref), + ) } /// Get a reference to the source map's diagnostics. pub fn diagnostics(&self) -> &[ExpressionStoreDiagnostics] { - &self.diagnostics - } - - fn shrink_to_fit(&mut self) { - let Self { - expr_map, - expr_map_back, - pat_map, - pat_map_back, - label_map, - label_map_back, - field_map_back, - pat_field_map_back, - expansions, - template_map, - diagnostics, - binding_definitions, - types_map, - types_map_back, - lifetime_map_back, - lifetime_map, - } = self; - if let Some(template_map) = template_map { - let FormatTemplate { - format_args_to_captures, - asm_to_captures, - implicit_capture_to_source, - } = &mut **template_map; - format_args_to_captures.shrink_to_fit(); - asm_to_captures.shrink_to_fit(); - implicit_capture_to_source.shrink_to_fit(); - } - expr_map.shrink_to_fit(); - expr_map_back.shrink_to_fit(); - pat_map.shrink_to_fit(); - pat_map_back.shrink_to_fit(); - label_map.shrink_to_fit(); - label_map_back.shrink_to_fit(); - field_map_back.shrink_to_fit(); - pat_field_map_back.shrink_to_fit(); - expansions.shrink_to_fit(); - diagnostics.shrink_to_fit(); - binding_definitions.shrink_to_fit(); - types_map.shrink_to_fit(); - types_map_back.shrink_to_fit(); - lifetime_map.shrink_to_fit(); - lifetime_map_back.shrink_to_fit(); + self.expr_only().map(|it| &*it.diagnostics).unwrap_or_default() } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs index fb6d931e0e4..c955393b9cf 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs @@ -36,6 +36,7 @@ pub struct Body { impl ops::Deref for Body { type Target = ExpressionStore; + #[inline] fn deref(&self) -> &Self::Target { &self.store } @@ -61,6 +62,7 @@ pub struct BodySourceMap { impl ops::Deref for BodySourceMap { type Target = ExpressionStoreSourceMap; + #[inline] fn deref(&self) -> &Self::Target { &self.store } @@ -102,9 +104,7 @@ impl Body { } }; let module = def.module(db); - let (body, mut source_map) = - lower_body(db, def, file_id, module, params, body, is_async_fn); - source_map.store.shrink_to_fit(); + let (body, source_map) = lower_body(db, def, file_id, module, params, body, is_async_fn); (Arc::new(body), Arc::new(source_map)) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index c0e51b338b4..4e877748ca2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -121,14 +121,10 @@ pub(super) fn lower_body( params = (0..count).map(|_| collector.missing_pat()).collect(); }; let body_expr = collector.missing_expr(); + let (store, source_map) = collector.store.finish(); return ( - Body { - store: collector.store.finish(), - params: params.into_boxed_slice(), - self_param, - body_expr, - }, - BodySourceMap { self_param: source_map_self_param, store: collector.source_map }, + Body { store, params: params.into_boxed_slice(), self_param, body_expr }, + BodySourceMap { self_param: source_map_self_param, store: source_map }, ); } @@ -171,14 +167,10 @@ pub(super) fn lower_body( }, ); + let (store, source_map) = collector.store.finish(); ( - Body { - store: collector.store.finish(), - params: params.into_boxed_slice(), - self_param, - body_expr, - }, - BodySourceMap { self_param: source_map_self_param, store: collector.source_map }, + Body { store, params: params.into_boxed_slice(), self_param, body_expr }, + BodySourceMap { self_param: source_map_self_param, store: source_map }, ) } @@ -190,7 +182,8 @@ pub(crate) fn lower_type_ref( let mut expr_collector = ExprCollector::new(db, module, type_ref.file_id); let type_ref = expr_collector.lower_type_ref_opt(type_ref.value, &mut ExprCollector::impl_trait_allocator); - (expr_collector.store.finish(), expr_collector.source_map, type_ref) + let (store, source_map) = expr_collector.store.finish(); + (store, source_map, type_ref) } pub(crate) fn lower_generic_params( @@ -205,7 +198,8 @@ pub(crate) fn lower_generic_params( let mut collector = generics::GenericParamsCollector::new(def); collector.lower(&mut expr_collector, param_list, where_clause); let params = collector.finish(); - (Arc::new(expr_collector.store.finish()), params, expr_collector.source_map) + let (store, source_map) = expr_collector.store.finish(); + (Arc::new(store), params, source_map) } pub(crate) fn lower_impl( @@ -232,7 +226,8 @@ pub(crate) fn lower_impl( impl_syntax.value.where_clause(), ); let params = collector.finish(); - (expr_collector.store.finish(), expr_collector.source_map, self_ty, trait_, params) + let (store, source_map) = expr_collector.store.finish(); + (store, source_map, self_ty, trait_, params) } pub(crate) fn lower_trait( @@ -253,7 +248,8 @@ pub(crate) fn lower_trait( trait_syntax.value.where_clause(), ); let params = collector.finish(); - (expr_collector.store.finish(), expr_collector.source_map, params) + let (store, source_map) = expr_collector.store.finish(); + (store, source_map, params) } pub(crate) fn lower_trait_alias( @@ -274,7 +270,8 @@ pub(crate) fn lower_trait_alias( trait_syntax.value.where_clause(), ); let params = collector.finish(); - (expr_collector.store.finish(), expr_collector.source_map, params) + let (store, source_map) = expr_collector.store.finish(); + (store, source_map, params) } pub(crate) fn lower_type_alias( @@ -313,7 +310,8 @@ pub(crate) fn lower_type_alias( .value .ty() .map(|ty| expr_collector.lower_type_ref(ty, &mut ExprCollector::impl_trait_allocator)); - (expr_collector.store.finish(), expr_collector.source_map, params, bounds, type_ref) + let (store, source_map) = expr_collector.store.finish(); + (store, source_map, params, bounds, type_ref) } pub(crate) fn lower_function( @@ -421,9 +419,10 @@ pub(crate) fn lower_function( } else { return_type }; + let (store, source_map) = expr_collector.store.finish(); ( - expr_collector.store.finish(), - expr_collector.source_map, + store, + source_map, generics, params.into_boxed_slice(), return_type, @@ -440,7 +439,6 @@ pub struct ExprCollector<'db> { local_def_map: &'db LocalDefMap, module: ModuleId, pub store: ExpressionStoreBuilder, - pub(crate) source_map: ExpressionStoreSourceMap, // state stuff // Prevent nested impl traits like `impl Foo<impl Bar>`. @@ -551,7 +549,6 @@ impl ExprCollector<'_> { module, def_map, local_def_map, - source_map: ExpressionStoreSourceMap::default(), store: ExpressionStoreBuilder::default(), expander, current_try_block_label: None, @@ -698,7 +695,7 @@ impl ExprCollector<'_> { let id = self.collect_macro_call(mcall, macro_ptr, true, |this, expansion| { this.lower_type_ref_opt(expansion, impl_trait_lower_fn) }); - self.source_map.types_map.insert(src, id); + self.store.types_map.insert(src, id); return id; } None => TypeRef::Error, @@ -732,8 +729,8 @@ impl ExprCollector<'_> { fn alloc_type_ref(&mut self, type_ref: TypeRef, node: TypePtr) -> TypeRefId { let id = self.store.types.alloc(type_ref); let ptr = self.expander.in_file(node); - self.source_map.types_map_back.insert(id, ptr); - self.source_map.types_map.insert(ptr, id); + self.store.types_map_back.insert(id, ptr); + self.store.types_map.insert(ptr, id); id } @@ -744,8 +741,8 @@ impl ExprCollector<'_> { ) -> LifetimeRefId { let id = self.store.lifetimes.alloc(lifetime_ref); let ptr = self.expander.in_file(node); - self.source_map.lifetime_map_back.insert(id, ptr); - self.source_map.lifetime_map.insert(ptr, id); + self.store.lifetime_map_back.insert(id, ptr); + self.store.lifetime_map.insert(ptr, id); id } @@ -1190,14 +1187,14 @@ impl ExprCollector<'_> { } ast::Expr::ContinueExpr(e) => { let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| { - self.source_map.diagnostics.push(e); + self.store.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); + self.store.diagnostics.push(e); None }); let expr = e.expr().map(|e| self.collect_expr(e)); @@ -1207,7 +1204,7 @@ impl ExprCollector<'_> { let inner = self.collect_expr_opt(e.expr()); // make the paren expr point to the inner expression as well for IDE resolution let src = self.expander.in_file(syntax_ptr); - self.source_map.expr_map.insert(src, inner.into()); + self.store.expr_map.insert(src, inner.into()); inner } ast::Expr::ReturnExpr(e) => { @@ -1248,7 +1245,7 @@ impl ExprCollector<'_> { None => self.missing_expr(), }; let src = self.expander.in_file(AstPtr::new(&field)); - self.source_map.field_map_back.insert(expr, src); + self.store.field_map_back.insert(expr, src); Some(RecordLitField { name, expr }) }) .collect(); @@ -1271,12 +1268,10 @@ impl ExprCollector<'_> { ast::Expr::AwaitExpr(e) => { let expr = self.collect_expr_opt(e.expr()); if let Awaitable::No(location) = self.is_lowering_awaitable_block() { - self.source_map.diagnostics.push( - ExpressionStoreDiagnostics::AwaitOutsideOfAsync { - node: self.expander.in_file(AstPtr::new(&e)), - location: location.to_string(), - }, - ); + self.store.diagnostics.push(ExpressionStoreDiagnostics::AwaitOutsideOfAsync { + node: self.expander.in_file(AstPtr::new(&e)), + location: location.to_string(), + }); } self.alloc_expr(Expr::Await { expr }, syntax_ptr) } @@ -1442,7 +1437,7 @@ impl ExprCollector<'_> { // Make the macro-call point to its expanded expression so we can query // semantics on syntax pointers to the macro let src = self.expander.in_file(syntax_ptr); - self.source_map.expr_map.insert(src, id.into()); + self.store.expr_map.insert(src, id.into()); id } None => self.alloc_expr(Expr::Missing, syntax_ptr), @@ -1486,7 +1481,7 @@ impl ExprCollector<'_> { let expr = self.collect_expr(expr); // Do not use `alloc_pat_from_expr()` here, it will override the entry in `expr_map`. let id = self.store.pats.alloc(Pat::Expr(expr)); - self.source_map.pat_map_back.insert(id, src); + self.store.pat_map_back.insert(id, src); id }) } @@ -1555,7 +1550,7 @@ impl ExprCollector<'_> { let id = self.collect_macro_call(e, macro_ptr, true, |this, expansion| { this.collect_expr_as_pat_opt(expansion) }); - self.source_map.expr_map.insert(src, id.into()); + self.store.expr_map.insert(src, id.into()); id } ast::Expr::RecordExpr(e) => { @@ -1576,7 +1571,7 @@ impl ExprCollector<'_> { let pat = self.collect_expr_as_pat(field_expr); let name = f.field_name()?.as_name(); let src = self.expander.in_file(AstPtr::new(&f).wrap_left()); - self.source_map.pat_field_map_back.insert(pat, src); + self.store.pat_field_map_back.insert(pat, src); Some(RecordFieldPat { name, pat }) }) .collect(); @@ -1622,7 +1617,7 @@ impl ExprCollector<'_> { ); if let Either::Left(pat) = pat { let src = this.expander.in_file(AstPtr::new(&expr).wrap_left()); - this.source_map.pat_map_back.insert(pat, src); + this.store.pat_map_back.insert(pat, src); } pat } @@ -1968,7 +1963,7 @@ impl ExprCollector<'_> { self.module.krate(), resolver, &mut |ptr, call| { - _ = self.source_map.expansions.insert(ptr.map(|(it, _)| it), call); + _ = self.store.expansions.insert(ptr.map(|(it, _)| it), call); }, ) } @@ -1978,19 +1973,17 @@ impl ExprCollector<'_> { Ok(res) => res, Err(UnresolvedMacro { path }) => { if record_diagnostics { - self.source_map.diagnostics.push( - ExpressionStoreDiagnostics::UnresolvedMacroCall { - node: self.expander.in_file(syntax_ptr), - path, - }, - ); + self.store.diagnostics.push(ExpressionStoreDiagnostics::UnresolvedMacroCall { + node: self.expander.in_file(syntax_ptr), + path, + }); } return collector(self, None); } }; if record_diagnostics { if let Some(err) = res.err { - self.source_map + self.store .diagnostics .push(ExpressionStoreDiagnostics::MacroError { node: macro_call_ptr, err }); } @@ -2001,7 +1994,7 @@ impl ExprCollector<'_> { // Keep collecting even with expansion errors so we can provide completions and // other services in incomplete macro expressions. if let Some(macro_file) = self.expander.current_file_id().macro_file() { - self.source_map.expansions.insert(macro_call_ptr, macro_file); + self.store.expansions.insert(macro_call_ptr, macro_file); } if record_diagnostics { @@ -2050,7 +2043,7 @@ impl ExprCollector<'_> { // Make the macro-call point to its expanded expression so we can query // semantics on syntax pointers to the macro let src = self.expander.in_file(syntax_ptr); - self.source_map.expr_map.insert(src, tail.into()); + self.store.expr_map.insert(src, tail.into()); }) } @@ -2361,7 +2354,7 @@ impl ExprCollector<'_> { let pat = self.collect_pat(ast_pat, binding_list); let name = f.field_name()?.as_name(); let src = self.expander.in_file(AstPtr::new(&f).wrap_right()); - self.source_map.pat_field_map_back.insert(pat, src); + self.store.pat_field_map_back.insert(pat, src); Some(RecordFieldPat { name, pat }) }) .collect(); @@ -2424,7 +2417,7 @@ impl ExprCollector<'_> { self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { this.collect_pat_opt(expanded_pat, binding_list) }); - self.source_map.pat_map.insert(src, pat.into()); + self.store.pat_map.insert(src, pat.into()); return pat; } None => Pat::Missing, @@ -2515,7 +2508,7 @@ impl ExprCollector<'_> { } }); if let Some(pat) = pat.left() { - self.source_map.pat_map.insert(src, pat.into()); + self.store.pat_map.insert(src, pat.into()); } pat } @@ -2537,7 +2530,7 @@ impl ExprCollector<'_> { match enabled { Ok(()) => true, Err(cfg) => { - self.source_map.diagnostics.push(ExpressionStoreDiagnostics::InactiveCode { + self.store.diagnostics.push(ExpressionStoreDiagnostics::InactiveCode { node: self.expander.in_file(SyntaxNodePtr::new(owner.syntax())), cfg, opts: self.cfg_options.clone(), @@ -2548,7 +2541,7 @@ impl ExprCollector<'_> { } fn add_definition_to_binding(&mut self, binding_id: BindingId, pat_id: PatId) { - self.source_map.binding_definitions.entry(binding_id).or_default().push(pat_id); + self.store.binding_definitions.entry(binding_id).or_default().push(pat_id); } // region: labels @@ -2724,7 +2717,7 @@ impl ExprCollector<'_> { |name, range| { let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name))); if let Some(range) = range { - self.source_map + self.store .template_map .get_or_insert_with(Default::default) .implicit_capture_to_source @@ -2836,7 +2829,7 @@ impl ExprCollector<'_> { ) }; - self.source_map + self.store .template_map .get_or_insert_with(Default::default) .format_args_to_captures @@ -3386,8 +3379,8 @@ impl ExprCollector<'_> { fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { let src = self.expander.in_file(ptr); let id = self.store.exprs.alloc(expr); - self.source_map.expr_map_back.insert(id, src.map(AstPtr::wrap_left)); - self.source_map.expr_map.insert(src, id.into()); + self.store.expr_map_back.insert(id, src.map(AstPtr::wrap_left)); + self.store.expr_map.insert(src, id.into()); id } // FIXME: desugared exprs don't have ptr, that's wrong and should be fixed. @@ -3398,9 +3391,9 @@ impl ExprCollector<'_> { fn alloc_expr_desugared_with_ptr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { let src = self.expander.in_file(ptr); let id = self.store.exprs.alloc(expr); - self.source_map.expr_map_back.insert(id, src.map(AstPtr::wrap_left)); + self.store.expr_map_back.insert(id, src.map(AstPtr::wrap_left)); // We intentionally don't fill this as it could overwrite a non-desugared entry - // self.source_map.expr_map.insert(src, id); + // self.store.expr_map.insert(src, id); id } fn missing_expr(&mut self) -> ExprId { @@ -3423,24 +3416,24 @@ impl ExprCollector<'_> { fn alloc_pat_from_expr(&mut self, pat: Pat, ptr: ExprPtr) -> PatId { let src = self.expander.in_file(ptr); let id = self.store.pats.alloc(pat); - self.source_map.expr_map.insert(src, id.into()); - self.source_map.pat_map_back.insert(id, src.map(AstPtr::wrap_left)); + self.store.expr_map.insert(src, id.into()); + self.store.pat_map_back.insert(id, src.map(AstPtr::wrap_left)); id } fn alloc_expr_from_pat(&mut self, expr: Expr, ptr: PatPtr) -> ExprId { let src = self.expander.in_file(ptr); let id = self.store.exprs.alloc(expr); - self.source_map.pat_map.insert(src, id.into()); - self.source_map.expr_map_back.insert(id, src.map(AstPtr::wrap_right)); + self.store.pat_map.insert(src, id.into()); + self.store.expr_map_back.insert(id, src.map(AstPtr::wrap_right)); id } fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { let src = self.expander.in_file(ptr); let id = self.store.pats.alloc(pat); - self.source_map.pat_map_back.insert(id, src.map(AstPtr::wrap_right)); - self.source_map.pat_map.insert(src, id.into()); + self.store.pat_map_back.insert(id, src.map(AstPtr::wrap_right)); + self.store.pat_map.insert(src, id.into()); id } // FIXME: desugared pats don't have ptr, that's wrong and should be fixed somehow. @@ -3454,8 +3447,8 @@ impl ExprCollector<'_> { fn alloc_label(&mut self, label: Label, ptr: LabelPtr) -> LabelId { let src = self.expander.in_file(ptr); let id = self.store.labels.alloc(label); - self.source_map.label_map_back.insert(id, src); - self.source_map.label_map.insert(src, id); + self.store.label_map_back.insert(id, src); + self.store.label_map.insert(src, id); id } // FIXME: desugared labels don't have ptr, that's wrong and should be fixed somehow. diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs index d36e5205c73..3bc4afb5c8a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs @@ -10,7 +10,7 @@ use tt::TextRange; use crate::{ expr_store::lower::{ExprCollector, FxIndexSet}, - hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmRegOrRegClass}, + hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmKind, InlineAsmRegOrRegClass}, }; impl ExprCollector<'_> { @@ -269,11 +269,20 @@ impl ExprCollector<'_> { } }) }; + + let kind = if asm.global_asm_token().is_some() { + InlineAsmKind::GlobalAsm + } else if asm.naked_asm_token().is_some() { + InlineAsmKind::NakedAsm + } else { + InlineAsmKind::Asm + }; + let idx = self.alloc_expr( - Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options }), + Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options, kind }), syntax_ptr, ); - self.source_map + self.store .template_map .get_or_insert_with(Default::default) .asm_to_captures diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/path/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/path/tests.rs index 8fd81c7b3df..f507841a91b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/path/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/path/tests.rs @@ -23,7 +23,7 @@ fn lower_path(path: ast::Path) -> (TestDB, ExpressionStore, Option<Path>) { let mut ctx = ExprCollector::new(&db, crate_def_map(&db, krate).root_module_id(), file_id.into()); let lowered_path = ctx.lower_path(path, &mut ExprCollector::impl_trait_allocator); - let store = ctx.store.finish(); + let (store, _) = ctx.store.finish(); (db, store, lowered_path) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs index 87bcd33ed7b..f1b011333d9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs @@ -902,7 +902,7 @@ impl Printer<'_> { let mut same_name = false; if let Pat::Bind { id, subpat: None } = &self.store[arg.pat] { if let Binding { name, mode: BindingAnnotation::Unannotated, .. } = - &self.store.bindings[*id] + &self.store.assert_expr_only().bindings[*id] { if name.as_str() == field_name { same_name = true; @@ -1063,7 +1063,7 @@ impl Printer<'_> { } fn print_binding(&mut self, id: BindingId) { - let Binding { name, mode, .. } = &self.store.bindings[id]; + let Binding { name, mode, .. } = &self.store.assert_expr_only().bindings[id]; let mode = match mode { BindingAnnotation::Unannotated => "", BindingAnnotation::Mutable => "mut ", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs index 2dd0b9bdb86..1952dae9d71 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs @@ -106,7 +106,9 @@ impl ExprScopes { let mut scopes = ExprScopes { scopes: Arena::default(), scope_entries: Arena::default(), - scope_by_expr: ArenaMap::with_capacity(body.exprs.len()), + scope_by_expr: ArenaMap::with_capacity( + body.expr_only.as_ref().map_or(0, |it| it.exprs.len()), + ), }; let mut root = scopes.root_scope(); if let Some(self_param) = body.self_param { @@ -179,7 +181,7 @@ impl ExprScopes { binding: BindingId, hygiene: HygieneId, ) { - let Binding { name, .. } = &store.bindings[binding]; + let Binding { name, .. } = &store[binding]; let entry = self.scope_entries.alloc(ScopeEntry { name: name.clone(), binding, hygiene }); self.scopes[scope].entries = IdxRange::new_inclusive(self.scopes[scope].entries.start()..=entry); @@ -251,7 +253,7 @@ fn compute_expr_scopes( scope: &mut ScopeId, ) { let make_label = - |label: &Option<LabelId>| label.map(|label| (label, store.labels[label].name.clone())); + |label: &Option<LabelId>| label.map(|label| (label, store[label].name.clone())); let compute_expr_scopes = |scopes: &mut ExprScopes, expr: ExprId, scope: &mut ScopeId| { compute_expr_scopes(expr, store, scopes, scope) @@ -534,9 +536,8 @@ fn foo() { }; let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap(); - let pat_src = source_map - .pat_syntax(*source_map.binding_definitions[resolved.binding()].first().unwrap()) - .unwrap(); + let pat_src = + source_map.pat_syntax(source_map.patterns_for_binding(resolved.binding())[0]).unwrap(); let local_name = pat_src.value.syntax_node_ptr().to_node(file.syntax()); assert_eq!(local_name.text_range(), expected_name.syntax().text_range()); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs index 927e280d739..c31428be28f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs @@ -508,9 +508,9 @@ fn f() { } "#, ); - assert_eq!(body.bindings.len(), 1, "should have a binding for `B`"); + assert_eq!(body.assert_expr_only().bindings.len(), 1, "should have a binding for `B`"); assert_eq!( - body.bindings[BindingId::from_raw(RawIdx::from_u32(0))].name.as_str(), + body[BindingId::from_raw(RawIdx::from_u32(0))].name.as_str(), "B", "should have a binding for `B`", ); @@ -566,6 +566,7 @@ const fn f(x: i32) -> i32 { ); let mtch_arms = body + .assert_expr_only() .exprs .iter() .find_map(|(_, expr)| { @@ -578,10 +579,10 @@ const fn f(x: i32) -> i32 { .unwrap(); let MatchArm { pat, .. } = mtch_arms[1]; - match body.pats[pat] { + match body[pat] { Pat::Range { start, end } => { - let hir_start = &body.exprs[start.unwrap()]; - let hir_end = &body.exprs[end.unwrap()]; + let hir_start = &body[start.unwrap()]; + let hir_end = &body[end.unwrap()]; assert!(matches!(hir_start, Expr::Path { .. })); assert!(matches!(hir_end, Expr::Path { .. })); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs index 0fc7857d978..e70cd2cd6c5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs @@ -332,6 +332,17 @@ pub struct OffsetOf { pub struct InlineAsm { pub operands: Box<[(Option<Name>, AsmOperand)]>, pub options: AsmOptions, + pub kind: InlineAsmKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InlineAsmKind { + /// `asm!()`. + Asm, + /// `global_asm!()`. + GlobalAsm, + /// `naked_asm!()`. + NakedAsm, } #[derive(Clone, Copy, PartialEq, Eq, Hash)] 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 f3273667158..5ab61c89394 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 @@ -143,6 +143,8 @@ impl<'a> Ctx<'a> { ast::Item::MacroRules(ast) => self.lower_macro_rules(ast)?.into(), ast::Item::MacroDef(ast) => self.lower_macro_def(ast)?.into(), ast::Item::ExternBlock(ast) => self.lower_extern_block(ast).into(), + // FIXME: Handle `global_asm!()`. + ast::Item::AsmExpr(_) => return None, }; let attrs = RawAttrs::new(self.db, item, self.span_map()); self.add_attrs(mod_item.ast_id(), attrs); 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 5923b3ea491..91b42bef8f7 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 @@ -35,10 +35,10 @@ use a::{c, d::{e}}; #![no_std] #![doc = " another file comment"] - // AstId: ExternCrate[5A82, 0] + // AstId: ExternCrate[070B, 0] pub(self) extern crate self as renamed; - // AstId: ExternCrate[7E1C, 0] + // AstId: ExternCrate[1EA5, 0] pub(in super) extern crate bli; // AstId: Use[0000, 0] @@ -78,15 +78,15 @@ extern "C" { // AstId: ExternBlock[0000, 0] extern { #[on_extern_type] - // AstId: TypeAlias[9FDF, 0] + // AstId: TypeAlias[A09C, 0] pub(self) type ExType; #[on_extern_static] - // AstId: Static[43C1, 0] + // AstId: Static[D85E, 0] pub(self) static EX_STATIC = _; #[on_extern_fn] - // AstId: Fn[452D, 0] + // AstId: Fn[B240, 0] pub(self) fn ex_fn; } "#]], @@ -124,20 +124,20 @@ enum E { } "#, expect![[r#" - // AstId: Struct[DFF3, 0] + // AstId: Struct[ED35, 0] pub(self) struct Unit; #[derive(Debug)] - // AstId: Struct[C7A1, 0] + // AstId: Struct[A47C, 0] pub(self) struct Struct { ... } - // AstId: Struct[DAC2, 0] + // AstId: Struct[C8C9, 0] pub(self) struct Tuple(...); - // AstId: Union[2DBB, 0] + // AstId: Union[2797, 0] pub(self) union Ize { ... } - // AstId: Enum[7FF8, 0] + // AstId: Enum[7D23, 0] pub(self) enum E { ... } "#]], ); @@ -162,18 +162,18 @@ trait Tr: SuperTrait + 'lifetime { } "#, expect![[r#" - // AstId: Static[B393, 0] + // AstId: Static[F7C1, 0] pub static ST = _; - // AstId: Const[B309, 0] + // AstId: Const[84BB, 0] pub(self) const _ = _; #[attr] #[inner_attr_in_fn] - // AstId: Fn[75E3, 0] + // AstId: Fn[BE8F, 0] pub(self) fn f; - // AstId: Trait[2998, 0] + // AstId: Trait[9320, 0] pub(self) trait Tr { ... } "#]], ); @@ -197,16 +197,16 @@ mod outline; expect![[r##" #[doc = " outer"] #[doc = " inner"] - // AstId: Module[CF93, 0] + // AstId: Module[03AE, 0] pub(self) mod inline { // AstId: Use[0000, 0] pub(self) use super::*; - // AstId: Fn[1B26, 0] + // AstId: Fn[2A78, 0] pub(self) fn fn_in_module; } - // AstId: Module[8994, 0] + // AstId: Module[C08B, 0] pub(self) mod outline; "##]], ); @@ -225,13 +225,13 @@ pub macro m2() {} m!(); "#, expect![[r#" - // AstId: MacroRules[88CE, 0] + // AstId: MacroRules[7E68, 0] macro_rules! m { ... } - // AstId: MacroDef[DC34, 0] + // AstId: MacroDef[1C1E, 0] pub macro m2 { ... } - // AstId: MacroCall[612F, 0], SyntaxContextId: ROOT2024, ExpandTo: Items + // AstId: MacroCall[7E68, 0], SyntaxContextId: ROOT2024, ExpandTo: Items m!(...); "#]], ); @@ -244,7 +244,7 @@ fn pub_self() { pub(self) struct S; "#, expect![[r#" - // AstId: Struct[42E2, 0] + // AstId: Struct[5024, 0] pub(self) struct S; "#]], ) 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 293868df613..1c3af47d522 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 @@ -28,6 +28,19 @@ fn test_asm_expand() { r#" #[rustc_builtin_macro] macro_rules! asm {() => {}} +#[rustc_builtin_macro] +macro_rules! global_asm {() => {}} +#[rustc_builtin_macro] +macro_rules! naked_asm {() => {}} + +global_asm! { + "" +} + +#[unsafe(naked)] +extern "C" fn foo() { + naked_asm!(""); +} fn main() { let i: u64 = 3; @@ -45,6 +58,17 @@ fn main() { expect![[r##" #[rustc_builtin_macro] macro_rules! asm {() => {}} +#[rustc_builtin_macro] +macro_rules! global_asm {() => {}} +#[rustc_builtin_macro] +macro_rules! naked_asm {() => {}} + +builtin #global_asm ("") + +#[unsafe(naked)] +extern "C" fn foo() { + builtin #naked_asm (""); +} fn main() { let i: u64 = 3; 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 c6d901ec93b..c489c1f7c1d 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 @@ -35,9 +35,9 @@ macro_rules! f { }; } -struct#0:MacroRules[8C8E, 0]@58..64#14336# MyTraitMap2#0:MacroCall[D499, 0]@31..42#ROOT2024# {#0:MacroRules[8C8E, 0]@72..73#14336# - map#0:MacroRules[8C8E, 0]@86..89#14336#:#0:MacroRules[8C8E, 0]@89..90#14336# #0:MacroRules[8C8E, 0]@89..90#14336#::#0:MacroRules[8C8E, 0]@91..93#14336#std#0:MacroRules[8C8E, 0]@93..96#14336#::#0:MacroRules[8C8E, 0]@96..98#14336#collections#0:MacroRules[8C8E, 0]@98..109#14336#::#0:MacroRules[8C8E, 0]@109..111#14336#HashSet#0:MacroRules[8C8E, 0]@111..118#14336#<#0:MacroRules[8C8E, 0]@118..119#14336#(#0:MacroRules[8C8E, 0]@119..120#14336#)#0:MacroRules[8C8E, 0]@120..121#14336#>#0:MacroRules[8C8E, 0]@121..122#14336#,#0:MacroRules[8C8E, 0]@122..123#14336# -}#0:MacroRules[8C8E, 0]@132..133#14336# +struct#0:MacroRules[BE8F, 0]@58..64#14336# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#14336# + map#0:MacroRules[BE8F, 0]@86..89#14336#:#0:MacroRules[BE8F, 0]@89..90#14336# #0:MacroRules[BE8F, 0]@89..90#14336#::#0:MacroRules[BE8F, 0]@91..93#14336#std#0:MacroRules[BE8F, 0]@93..96#14336#::#0:MacroRules[BE8F, 0]@96..98#14336#collections#0:MacroRules[BE8F, 0]@98..109#14336#::#0:MacroRules[BE8F, 0]@109..111#14336#HashSet#0:MacroRules[BE8F, 0]@111..118#14336#<#0:MacroRules[BE8F, 0]@118..119#14336#(#0:MacroRules[BE8F, 0]@119..120#14336#)#0:MacroRules[BE8F, 0]@120..121#14336#>#0:MacroRules[BE8F, 0]@121..122#14336#,#0:MacroRules[BE8F, 0]@122..123#14336# +}#0:MacroRules[BE8F, 0]@132..133#14336# "#]], ); } @@ -75,12 +75,12 @@ macro_rules! f { }; } -fn#0:MacroCall[D499, 0]@30..32#ROOT2024# main#0:MacroCall[D499, 0]@33..37#ROOT2024#(#0:MacroCall[D499, 0]@37..38#ROOT2024#)#0:MacroCall[D499, 0]@38..39#ROOT2024# {#0:MacroCall[D499, 0]@40..41#ROOT2024# - 1#0:MacroCall[D499, 0]@50..51#ROOT2024#;#0:MacroCall[D499, 0]@51..52#ROOT2024# - 1.0#0:MacroCall[D499, 0]@61..64#ROOT2024#;#0:MacroCall[D499, 0]@64..65#ROOT2024# - (#0:MacroCall[D499, 0]@74..75#ROOT2024#(#0:MacroCall[D499, 0]@75..76#ROOT2024#1#0:MacroCall[D499, 0]@76..77#ROOT2024#,#0:MacroCall[D499, 0]@77..78#ROOT2024# )#0:MacroCall[D499, 0]@78..79#ROOT2024#,#0:MacroCall[D499, 0]@79..80#ROOT2024# )#0:MacroCall[D499, 0]@80..81#ROOT2024#.#0:MacroCall[D499, 0]@81..82#ROOT2024#0#0:MacroCall[D499, 0]@82..85#ROOT2024#.#0:MacroCall[D499, 0]@82..85#ROOT2024#0#0:MacroCall[D499, 0]@82..85#ROOT2024#;#0:MacroCall[D499, 0]@85..86#ROOT2024# - let#0:MacroCall[D499, 0]@95..98#ROOT2024# x#0:MacroCall[D499, 0]@99..100#ROOT2024# =#0:MacroCall[D499, 0]@101..102#ROOT2024# 1#0:MacroCall[D499, 0]@103..104#ROOT2024#;#0:MacroCall[D499, 0]@104..105#ROOT2024# -}#0:MacroCall[D499, 0]@110..111#ROOT2024# +fn#0:MacroCall[BE8F, 0]@30..32#ROOT2024# main#0:MacroCall[BE8F, 0]@33..37#ROOT2024#(#0:MacroCall[BE8F, 0]@37..38#ROOT2024#)#0:MacroCall[BE8F, 0]@38..39#ROOT2024# {#0:MacroCall[BE8F, 0]@40..41#ROOT2024# + 1#0:MacroCall[BE8F, 0]@50..51#ROOT2024#;#0:MacroCall[BE8F, 0]@51..52#ROOT2024# + 1.0#0:MacroCall[BE8F, 0]@61..64#ROOT2024#;#0:MacroCall[BE8F, 0]@64..65#ROOT2024# + (#0:MacroCall[BE8F, 0]@74..75#ROOT2024#(#0:MacroCall[BE8F, 0]@75..76#ROOT2024#1#0:MacroCall[BE8F, 0]@76..77#ROOT2024#,#0:MacroCall[BE8F, 0]@77..78#ROOT2024# )#0:MacroCall[BE8F, 0]@78..79#ROOT2024#,#0:MacroCall[BE8F, 0]@79..80#ROOT2024# )#0:MacroCall[BE8F, 0]@80..81#ROOT2024#.#0:MacroCall[BE8F, 0]@81..82#ROOT2024#0#0:MacroCall[BE8F, 0]@82..85#ROOT2024#.#0:MacroCall[BE8F, 0]@82..85#ROOT2024#0#0:MacroCall[BE8F, 0]@82..85#ROOT2024#;#0:MacroCall[BE8F, 0]@85..86#ROOT2024# + let#0:MacroCall[BE8F, 0]@95..98#ROOT2024# x#0:MacroCall[BE8F, 0]@99..100#ROOT2024# =#0:MacroCall[BE8F, 0]@101..102#ROOT2024# 1#0:MacroCall[BE8F, 0]@103..104#ROOT2024#;#0:MacroCall[BE8F, 0]@104..105#ROOT2024# +}#0:MacroCall[BE8F, 0]@110..111#ROOT2024# "#]], @@ -171,7 +171,7 @@ fn main(foo: ()) { } fn main(foo: ()) { - /* error: unresolved macro unresolved */"helloworld!"#0:Fn[B9C7, 0]@236..321#ROOT2024#; + /* error: unresolved macro unresolved */"helloworld!"#0:Fn[15AE, 0]@236..321#ROOT2024#; } } @@ -197,7 +197,7 @@ macro_rules! mk_struct { #[macro_use] mod foo; -struct#1:MacroRules[E572, 0]@59..65#14336# Foo#0:MacroCall[BDD3, 0]@32..35#ROOT2024#(#1:MacroRules[E572, 0]@70..71#14336#u32#0:MacroCall[BDD3, 0]@41..44#ROOT2024#)#1:MacroRules[E572, 0]@74..75#14336#;#1:MacroRules[E572, 0]@75..76#14336# +struct#1:MacroRules[DB0C, 0]@59..65#14336# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#14336#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#14336#;#1:MacroRules[DB0C, 0]@75..76#14336# "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index 1c69b37f164..5e95b061399 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -20,13 +20,14 @@ use base_db::RootQueryDb; use expect_test::Expect; use hir_expand::{ AstId, InFile, MacroCallId, MacroCallKind, MacroKind, + builtin::quote::quote, db::ExpandDatabase, proc_macro::{ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind}, span_map::SpanMapRef, }; -use intern::Symbol; +use intern::{Symbol, sym}; use itertools::Itertools; -use span::{Edition, Span}; +use span::{Edition, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext}; use stdx::{format_to, format_to_acc}; use syntax::{ AstNode, AstPtr, @@ -34,7 +35,9 @@ use syntax::{ SyntaxNode, T, ast::{self, edit::IndentLevel}, }; +use syntax_bridge::token_tree_to_syntax_node; use test_fixture::WithFixture; +use tt::{TextRange, TextSize}; use crate::{ AdtId, Lookup, ModuleDefId, @@ -386,3 +389,38 @@ impl ProcMacroExpander for IdentityWhenValidProcMacroExpander { other.type_id() == TypeId::of::<Self>() } } + +#[test] +fn regression_20171() { + // This really isn't the appropriate place to put this test, but it's convenient with access to `quote!`. + let span = Span { + range: TextRange::empty(TextSize::new(0)), + anchor: SpanAnchor { + file_id: span::EditionedFileId::current_edition(span::FileId::from_raw(0)), + ast_id: ROOT_ERASED_FILE_AST_ID, + }, + ctx: SyntaxContext::root(Edition::CURRENT), + }; + let close_brace = tt::Punct { char: '}', spacing: tt::Spacing::Alone, span }; + let dotdot1 = tt::Punct { char: '.', spacing: tt::Spacing::Joint, span }; + let dotdot2 = tt::Punct { char: '.', spacing: tt::Spacing::Alone, span }; + let dollar_crate = sym::dollar_crate; + let tt = quote! { + span => { + if !((matches!( + drive_parser(&mut parser, data, false), + Err(TarParserError::CorruptField { + field: CorruptFieldContext::PaxKvLength, + error: GeneralParseError::ParseInt(ParseIntError { #dotdot1 #dotdot2 }) + }) + #close_brace ))) { + #dollar_crate::panic::panic_2021!(); + }} + }; + token_tree_to_syntax_node( + &tt, + syntax_bridge::TopEntryPoint::MacroStmts, + &mut |_| Edition::CURRENT, + Edition::CURRENT, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs index d5ae6f8d885..6952a9da101 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs @@ -181,9 +181,9 @@ fn foo(&self) { self.0. 1; } -fn#0:Fn[4D85, 0]@45..47#ROOT2024# foo#0:Fn[4D85, 0]@48..51#ROOT2024#(#0:Fn[4D85, 0]@51..52#ROOT2024#�:Fn[4D85, 0]@52..53#ROOT2024#self#0:Fn[4D85, 0]@53..57#ROOT2024# )#0:Fn[4D85, 0]@57..58#ROOT2024# {#0:Fn[4D85, 0]@59..60#ROOT2024# - self#0:Fn[4D85, 0]@65..69#ROOT2024# .#0:Fn[4D85, 0]@69..70#ROOT2024#0#0:Fn[4D85, 0]@70..71#ROOT2024#.#0:Fn[4D85, 0]@71..72#ROOT2024#1#0:Fn[4D85, 0]@73..74#ROOT2024#;#0:Fn[4D85, 0]@74..75#ROOT2024# -}#0:Fn[4D85, 0]@76..77#ROOT2024#"#]], +fn#0:Fn[8A31, 0]@45..47#ROOT2024# foo#0:Fn[8A31, 0]@48..51#ROOT2024#(#0:Fn[8A31, 0]@51..52#ROOT2024#�:Fn[8A31, 0]@52..53#ROOT2024#self#0:Fn[8A31, 0]@53..57#ROOT2024# )#0:Fn[8A31, 0]@57..58#ROOT2024# {#0:Fn[8A31, 0]@59..60#ROOT2024# + self#0:Fn[8A31, 0]@65..69#ROOT2024# .#0:Fn[8A31, 0]@69..70#ROOT2024#0#0:Fn[8A31, 0]@70..71#ROOT2024#.#0:Fn[8A31, 0]@71..72#ROOT2024#1#0:Fn[8A31, 0]@73..74#ROOT2024#;#0:Fn[8A31, 0]@74..75#ROOT2024# +}#0:Fn[8A31, 0]@76..77#ROOT2024#"#]], ); } 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 0837308d5b6..5030585147d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -373,19 +373,14 @@ pub fn crate_def_map(db: &dyn DefDatabase, crate_id: Crate) -> &DefMap { crate_local_def_map(db, crate_id).def_map(db) } -#[allow(unused_lifetimes)] -mod __ { - use super::*; - #[salsa_macros::tracked] - pub(crate) struct DefMapPair<'db> { - #[tracked] - #[returns(ref)] - pub(crate) def_map: DefMap, - #[returns(ref)] - pub(crate) local: LocalDefMap, - } +#[salsa_macros::tracked] +pub(crate) struct DefMapPair<'db> { + #[tracked] + #[returns(ref)] + pub(crate) def_map: DefMap, + #[returns(ref)] + pub(crate) local: LocalDefMap, } -pub(crate) use __::DefMapPair; #[salsa_macros::tracked(returns(ref))] pub(crate) fn crate_local_def_map(db: &dyn DefDatabase, crate_id: Crate) -> DefMapPair<'_> { 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 6f321980af4..316ad5dae69 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -1052,17 +1052,6 @@ impl<'db> Scope<'db> { } } -pub fn resolver_for_expr( - db: &dyn DefDatabase, - owner: DefWithBodyId, - expr_id: ExprId, -) -> Resolver<'_> { - let r = owner.resolver(db); - let scopes = db.expr_scopes(owner); - let scope_id = scopes.scope_for(expr_id); - resolver_for_scope_(db, scopes, scope_id, r, owner) -} - pub fn resolver_for_scope( db: &dyn DefDatabase, owner: DefWithBodyId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs index 1958eb6c6a1..92e610b36ac 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs @@ -779,14 +779,10 @@ impl VariantFields { Arc::new(VariantFields { fields, store: Arc::new(store), shape }), Arc::new(source_map), ), - None => ( - Arc::new(VariantFields { - fields: Arena::default(), - store: ExpressionStore::empty_singleton(), - shape, - }), - ExpressionStoreSourceMap::empty_singleton(), - ), + None => { + let (store, source_map) = ExpressionStore::empty_singleton(); + (Arc::new(VariantFields { fields: Arena::default(), store, shape }), source_map) + } } } @@ -878,7 +874,7 @@ fn lower_fields<Field: ast::HasAttrs + ast::HasVisibility>( idx += 1; } Err(cfg) => { - col.source_map.diagnostics.push( + col.store.diagnostics.push( crate::expr_store::ExpressionStoreDiagnostics::InactiveCode { node: InFile::new(fields.file_id, SyntaxNodePtr::new(field.syntax())), cfg, @@ -891,9 +887,9 @@ fn lower_fields<Field: ast::HasAttrs + ast::HasVisibility>( if !has_fields { return None; } - let store = col.store.finish(); + let (store, source_map) = col.store.finish(); arena.shrink_to_fit(); - Some((arena, store, col.source_map)) + Some((arena, store, source_map)) } #[derive(Debug, PartialEq, Eq)] @@ -980,7 +976,7 @@ impl EnumVariants { if !matches!(variant.shape, FieldsShape::Unit) { let body = db.body(v.into()); // A variant with explicit discriminant - if body.exprs[body.body_expr] != crate::hir::Expr::Missing { + if !matches!(body[body.body_expr], crate::hir::Expr::Missing) { return false; } } 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 800b40a9e7e..60fbc660652 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 @@ -125,8 +125,8 @@ register_builtin! { (assert, Assert) => assert_expand, (stringify, Stringify) => stringify_expand, (asm, Asm) => asm_expand, - (global_asm, GlobalAsm) => asm_expand, - (naked_asm, NakedAsm) => asm_expand, + (global_asm, GlobalAsm) => global_asm_expand, + (naked_asm, NakedAsm) => naked_asm_expand, (cfg, Cfg) => cfg_expand, (core_panic, CorePanic) => panic_expand, (std_panic, StdPanic) => panic_expand, @@ -325,6 +325,36 @@ fn asm_expand( ExpandResult::ok(expanded) } +fn global_asm_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::TopSubtree, + span: Span, +) -> ExpandResult<tt::TopSubtree> { + let mut tt = tt.clone(); + tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; + let pound = mk_pound(span); + let expanded = quote! {span => + builtin #pound global_asm #tt + }; + ExpandResult::ok(expanded) +} + +fn naked_asm_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::TopSubtree, + span: Span, +) -> ExpandResult<tt::TopSubtree> { + let mut tt = tt.clone(); + tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; + let pound = mk_pound(span); + let expanded = quote! {span => + builtin #pound naked_asm #tt + }; + ExpandResult::ok(expanded) +} + fn cfg_expand( db: &dyn ExpandDatabase, id: MacroCallId, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs index d5874f829ba..70c38d4d7c7 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs @@ -129,7 +129,7 @@ macro_rules! quote { } } } -pub(super) use quote; +pub use quote; pub trait ToTokenTree { fn to_tokens(self, span: Span, builder: &mut TopSubtreeBuilder); 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 679f61112ad..217d991d110 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs @@ -179,10 +179,9 @@ impl Name { self.symbol.as_str() } - #[inline] pub fn display<'a>( &'a self, - db: &dyn salsa::Database, + db: &dyn crate::db::ExpandDatabase, edition: Edition, ) -> impl fmt::Display + 'a { _ = db; 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 24530a5f67c..14b9cd203f6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -281,7 +281,7 @@ pub(crate) fn const_eval_discriminant_variant( let def = variant_id.into(); let body = db.body(def); let loc = variant_id.lookup(db); - if body.exprs[body.body_expr] == Expr::Missing { + if matches!(body[body.body_expr], Expr::Missing) { let prev_idx = loc.index.checked_sub(1); let value = match prev_idx { Some(prev_idx) => { @@ -334,7 +334,7 @@ pub(crate) fn eval_to_const( // Type checking clousres need an isolated body (See the above FIXME). Bail out early to prevent panic. return unknown_const(infer[expr].clone()); } - if let Expr::Path(p) = &ctx.body.exprs[expr] { + if let Expr::Path(p) = &ctx.body[expr] { let resolver = &ctx.resolver; if let Some(c) = path_to_const(db, resolver, p, mode, || ctx.generics(), debruijn, infer[expr].clone()) 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 5d3be07f3db..b3d46845c44 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -273,8 +273,9 @@ pub trait HirDatabase: DefDatabase + std::fmt::Debug { #[salsa::invoke(crate::variance::variances_of)] #[salsa::cycle( - cycle_fn = crate::variance::variances_of_cycle_fn, - cycle_initial = crate::variance::variances_of_cycle_initial, + // cycle_fn = crate::variance::variances_of_cycle_fn, + // cycle_initial = crate::variance::variances_of_cycle_initial, + cycle_result = crate::variance::variances_of_cycle_initial, )] fn variances_of(&self, def: GenericDefId) -> Option<Arc<[crate::variance::Variance]>>; 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 9c0f8f40080..40fe3073cf2 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 @@ -226,11 +226,10 @@ impl<'a> DeclValidator<'a> { let body = self.db.body(func.into()); let edition = self.edition(func); let mut pats_replacements = body - .pats - .iter() + .pats() .filter_map(|(pat_id, pat)| match pat { Pat::Bind { id, .. } => { - let bind_name = &body.bindings[*id].name; + let bind_name = &body[*id].name; let mut suggested_text = to_lower_snake_case(bind_name.as_str())?; if is_raw_identifier(&suggested_text, edition) { suggested_text.insert_str(0, "r#"); 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 5d56957be6d..5ae6bf6dffd 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 @@ -101,7 +101,7 @@ impl ExprValidator { self.check_for_trailing_return(body.body_expr, &body); } - for (id, expr) in body.exprs.iter() { + for (id, expr) in body.exprs() { if let Some((variant, missed_fields, true)) = record_literal_missing_fields(db, &self.infer, id, expr) { @@ -132,7 +132,7 @@ impl ExprValidator { } } - for (id, pat) in body.pats.iter() { + for (id, pat) in body.pats() { if let Some((variant, missed_fields, true)) = record_pattern_missing_fields(db, &self.infer, id, pat) { @@ -389,7 +389,7 @@ impl ExprValidator { if !self.validate_lints { return; } - match &body.exprs[body_expr] { + match &body[body_expr] { Expr::Block { statements, tail, .. } => { let last_stmt = tail.or_else(|| match statements.last()? { Statement::Expr { expr, .. } => Some(*expr), @@ -428,7 +428,7 @@ impl ExprValidator { if else_branch.is_none() { return; } - if let Expr::Block { statements, tail, .. } = &self.body.exprs[*then_branch] { + if let Expr::Block { statements, tail, .. } = &self.body[*then_branch] { let last_then_expr = tail.or_else(|| match statements.last()? { Statement::Expr { expr, .. } => Some(*expr), _ => None, 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 c3ab5aff3db..ca132fbdc45 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 @@ -150,7 +150,7 @@ impl<'a> PatCtxt<'a> { hir_def::hir::Pat::Bind { id, subpat, .. } => { let bm = self.infer.binding_modes[pat]; ty = &self.infer[id]; - let name = &self.body.bindings[id].name; + let name = &self.body[id].name; match (bm, ty.kind(Interner)) { (BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty, (BindingMode::Ref(_), _) => { 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 20cf3c78115..f6ad3c7aae2 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 @@ -7,7 +7,7 @@ use either::Either; use hir_def::{ AdtId, DefWithBodyId, FieldId, FunctionId, VariantId, expr_store::{Body, path::Path}, - hir::{AsmOperand, Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp}, + hir::{AsmOperand, Expr, ExprId, ExprOrPatId, InlineAsmKind, Pat, PatId, Statement, UnaryOp}, resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs}, signatures::StaticFlags, type_ref::Rawness, @@ -217,7 +217,7 @@ impl<'db> UnsafeVisitor<'db> { } fn walk_pat(&mut self, current: PatId) { - let pat = &self.body.pats[current]; + let pat = &self.body[current]; if self.inside_union_destructure { match pat { @@ -264,7 +264,7 @@ impl<'db> UnsafeVisitor<'db> { } fn walk_expr(&mut self, current: ExprId) { - let expr = &self.body.exprs[current]; + let expr = &self.body[current]; let inside_assignment = mem::replace(&mut self.inside_assignment, false); match expr { &Expr::Call { callee, .. } => { @@ -284,7 +284,7 @@ impl<'db> UnsafeVisitor<'db> { self.resolver.reset_to_guard(guard); } Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { - match self.body.exprs[*expr] { + match self.body[*expr] { // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, // see https://github.com/rust-lang/rust/pull/125834. Expr::Path(_) => return, @@ -315,7 +315,12 @@ impl<'db> UnsafeVisitor<'db> { self.inside_assignment = old_inside_assignment; } Expr::InlineAsm(asm) => { - self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm); + if asm.kind == InlineAsmKind::Asm { + // `naked_asm!()` requires `unsafe` on the attribute (`#[unsafe(naked)]`), + // and `global_asm!()` doesn't require it at all. + self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm); + } + asm.operands.iter().for_each(|(_, op)| match op { AsmOperand::In { expr, .. } | AsmOperand::Out { expr: Some(expr), .. } 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 810fe76f231..b3760e3a382 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -795,6 +795,14 @@ fn render_const_scalar( let Some(bytes) = memory_map.get(addr, size_one * count) else { return f.write_str("<ref-data-not-available>"); }; + let expected_len = count * size_one; + if bytes.len() < expected_len { + never!( + "Memory map size is too small. Expected {expected_len}, got {}", + bytes.len(), + ); + return f.write_str("<layout-error>"); + } f.write_str("&[")?; let mut first = true; for i in 0..count { @@ -2328,6 +2336,7 @@ impl HirDisplayWithExpressionStore for TypeBound { store[*path].hir_fmt(f, store) } TypeBound::Use(args) => { + write!(f, "use<")?; let edition = f.edition(); let last = args.len().saturating_sub(1); for (idx, arg) in args.iter().enumerate() { 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 index d2eaf212365..3f7eba9dd18 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -273,7 +273,7 @@ impl InferenceContext<'_> { 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 { + if self.body[b].mode == BindingAnnotation::RefMut { r = Mutability::Mut; } }); 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 99d3b5c7a84..18288b718f7 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 @@ -459,7 +459,7 @@ impl InferenceContext<'_> { expected: &Ty, decl: Option<DeclContext>, ) -> Ty { - let Binding { mode, .. } = self.body.bindings[binding]; + let Binding { mode, .. } = self.body[binding]; let mode = if mode == BindingAnnotation::Unannotated { default_bm } else { @@ -639,7 +639,7 @@ impl InferenceContext<'_> { 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!(body[pat], Pat::Bind { id, .. } if body.bindings[id].mode == BindingAnnotation::Ref); + res |= matches!(body[pat], Pat::Bind { id, .. } if body[id].mode == BindingAnnotation::Ref); }); res } 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 88c33ecccad..82d0ed4f194 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 @@ -2,7 +2,7 @@ use base_db::Crate; use hir_def::layout::TargetDataLayout; -use rustc_abi::{AlignFromBytesError, TargetDataLayoutErrors, AddressSpace}; +use rustc_abi::{AddressSpace, AlignFromBytesError, TargetDataLayoutErrors}; 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 cc7d74f4fb0..b3bc226ec93 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 @@ -119,8 +119,7 @@ fn eval_expr( .unwrap(); let hir_body = db.body(function_id.into()); let b = hir_body - .bindings - .iter() + .bindings() .find(|x| x.1.name.display_no_db(file_id.edition(&db)).to_smolstr() == "goal") .unwrap() .0; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index 06686b6a164..5c06234fa07 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -1018,8 +1018,12 @@ fn check_generic_args_len( } let lifetime_args_len = def_generics.len_lifetimes_self(); - if provided_lifetimes_count == 0 && lifetime_args_len > 0 && !lowering_assoc_type_generics { - // In generic associated types, we never allow inferring the lifetimes. + if provided_lifetimes_count == 0 + && lifetime_args_len > 0 + && (!lowering_assoc_type_generics || infer_args) + { + // In generic associated types, we never allow inferring the lifetimes, but only in type context, that is + // when `infer_args == false`. In expression/pattern context we always allow inferring them, even for GATs. match lifetime_elision { &LifetimeElisionKind::AnonymousCreateParameter { report_in_path } => { ctx.report_elided_lifetimes_in_path(def, lifetime_args_len as u32, report_in_path); 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 bf80ed7967a..482b420279c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -1212,10 +1212,9 @@ impl MirSpan { match *self { MirSpan::ExprId(expr) => matches!(body[expr], Expr::Ref { .. }), // FIXME: Figure out if this is correct wrt. match ergonomics. - MirSpan::BindingId(binding) => matches!( - body.bindings[binding].mode, - BindingAnnotation::Ref | BindingAnnotation::RefMut - ), + MirSpan::BindingId(binding) => { + matches!(body[binding].mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) + } MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false, } } 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 55fada14363..9a97bd6dbe2 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 @@ -31,8 +31,8 @@ use syntax::{SyntaxNodePtr, TextRange}; use triomphe::Arc; use crate::{ - CallableDefId, ClosureId, ComplexMemoryMap, Const, ConstData, ConstScalar, FnDefId, Interner, - MemoryMap, Substitution, ToChalk, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, + AliasTy, CallableDefId, ClosureId, ComplexMemoryMap, Const, ConstData, ConstScalar, FnDefId, + Interner, MemoryMap, Substitution, ToChalk, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, consteval::{ConstEvalError, intern_const_scalar, try_const_usize}, db::{HirDatabase, InternedClosure}, display::{ClosureStyle, DisplayTarget, HirDisplay}, @@ -2195,7 +2195,7 @@ impl Evaluator<'_> { } } } - chalk_ir::TyKind::Array(inner, len) => { + TyKind::Array(inner, len) => { let len = match try_const_usize(this.db, len) { Some(it) => it as usize, None => not_supported!("non evaluatable array len in patching addresses"), @@ -2213,7 +2213,7 @@ impl Evaluator<'_> { )?; } } - chalk_ir::TyKind::Tuple(_, subst) => { + 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 @@ -2229,7 +2229,7 @@ impl Evaluator<'_> { )?; } } - chalk_ir::TyKind::Adt(adt, subst) => match adt.0 { + TyKind::Adt(adt, subst) => match adt.0 { AdtId::StructId(s) => { let data = s.fields(this.db); let layout = this.layout(ty)?; @@ -2280,6 +2280,10 @@ impl Evaluator<'_> { } AdtId::UnionId(_) => (), }, + TyKind::Alias(AliasTy::Projection(proj)) => { + let ty = this.db.normalize_projection(proj.clone(), this.trait_env.clone()); + rec(this, bytes, &ty, locals, mm, stack_depth_limit - 1)?; + } _ => (), } Ok(()) 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 845d6b8eae1..07d81472729 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 @@ -321,7 +321,7 @@ impl<'ctx> MirLowerCtx<'ctx> { current: BasicBlockId, ) -> Result<Option<(Operand, BasicBlockId)>> { if !self.has_adjustments(expr_id) { - if let Expr::Literal(l) = &self.body.exprs[expr_id] { + if let Expr::Literal(l) = &self.body[expr_id] { let ty = self.expr_ty_without_adjust(expr_id); return Ok(Some((self.lower_literal_to_operand(ty, l)?, current))); } @@ -411,7 +411,7 @@ impl<'ctx> MirLowerCtx<'ctx> { place: Place, mut current: BasicBlockId, ) -> Result<Option<BasicBlockId>> { - match &self.body.exprs[expr_id] { + match &self.body[expr_id] { Expr::OffsetOf(_) => { not_supported!("builtin#offset_of") } @@ -1374,7 +1374,7 @@ impl<'ctx> MirLowerCtx<'ctx> { } fn lower_literal_or_const_to_operand(&mut self, ty: Ty, loc: &ExprId) -> Result<Operand> { - match &self.body.exprs[*loc] { + match &self.body[*loc] { Expr::Literal(l) => self.lower_literal_to_operand(ty, l), Expr::Path(c) => { let owner = self.owner; @@ -1850,7 +1850,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.drop_scopes.last_mut().unwrap().locals.push(local_id); if let Pat::Bind { id, subpat: None } = self.body[it] { if matches!( - self.body.bindings[id].mode, + self.body[id].mode, BindingAnnotation::Unannotated | BindingAnnotation::Mutable ) { self.result.binding_locals.insert(id, local_id); @@ -1859,7 +1859,7 @@ impl<'ctx> MirLowerCtx<'ctx> { local_id })); // and then rest of bindings - for (id, _) in self.body.bindings.iter() { + for (id, _) in self.body.bindings() { if !pick_binding(id) { continue; } @@ -2126,7 +2126,7 @@ pub fn mir_body_for_closure_query( .result .binding_locals .into_iter() - .filter(|it| ctx.body.binding_owners.get(&it.0).copied() == Some(expr)) + .filter(|it| ctx.body.binding_owner(it.0) == Some(expr)) .collect(); if let Some(err) = err { return Err(MirLowerError::UnresolvedUpvar(err)); @@ -2191,7 +2191,7 @@ pub fn lower_to_mir( // 0 is return local ctx.result.locals.alloc(Local { ty: ctx.expr_ty_after_adjustments(root_expr) }); let binding_picker = |b: BindingId| { - let owner = ctx.body.binding_owners.get(&b).copied(); + let owner = ctx.body.binding_owner(b); if root_expr == body.body_expr { owner.is_none() } else { owner == Some(root_expr) } }; // 1 to param_len is for params 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 e7bffead931..e074c2d558e 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 @@ -133,7 +133,7 @@ impl MirLowerCtx<'_> { } this.lower_expr_to_some_place_without_adjust(expr_id, current) }; - match &self.body.exprs[expr_id] { + match &self.body[expr_id] { Expr::Path(p) => { let resolver_guard = self.resolver.update_to_inner_scope(self.db, self.owner, expr_id); 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 index 61c0685c48a..3325226b1d3 100644 --- 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 @@ -130,7 +130,7 @@ impl MirLowerCtx<'_> { .collect::<Vec<_>>() .into(), ); - Ok(match &self.body.pats[pattern] { + Ok(match &self.body[pattern] { Pat::Missing => return Err(MirLowerError::IncompletePattern), Pat::Wild => (current, current_else), Pat::Tuple { args, ellipsis } => { @@ -436,7 +436,7 @@ impl MirLowerCtx<'_> { (next, Some(else_target)) } }, - Pat::Lit(l) => match &self.body.exprs[*l] { + Pat::Lit(l) => match &self.body[*l] { Expr::Literal(l) => { if mode == MatchingMode::Check { let c = self.lower_literal_to_operand(self.infer[pattern].clone(), l)?; 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 78a69cf4509..aad54f88438 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 @@ -219,7 +219,7 @@ impl<'a> MirPrettyCtx<'a> { fn local_name(&self, local: LocalId) -> LocalName { match self.local_to_binding.get(local) { - Some(b) => LocalName::Binding(self.hir_body.bindings[*b].name.clone(), local), + Some(b) => LocalName::Binding(self.hir_body[*b].name.clone(), local), None => LocalName::Unknown(local), } } 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 79754bc8a09..9605a0b4124 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs @@ -168,7 +168,7 @@ fn check_impl( let inference_result = db.infer(def); for (pat, mut ty) in inference_result.type_of_pat.iter() { - if let Pat::Bind { id, .. } = body.pats[pat] { + if let Pat::Bind { id, .. } = body[pat] { ty = &inference_result.type_of_binding[id]; } let node = match pat_node(&body_source_map, pat, &db) { @@ -316,7 +316,7 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { } for (pat, mut ty) in inference_result.type_of_pat.iter() { - if let Pat::Bind { id, .. } = body.pats[pat] { + if let Pat::Bind { id, .. } = body[pat] { ty = &inference_result.type_of_binding[id]; } let node = match body_source_map.pat_syntax(pat) { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs b/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs index 87d9df611bd..08a215fecf6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs @@ -54,14 +54,14 @@ pub(crate) fn variances_of(db: &dyn HirDatabase, def: GenericDefId) -> Option<Ar variances.is_empty().not().then(|| Arc::from_iter(variances)) } -pub(crate) fn variances_of_cycle_fn( - _db: &dyn HirDatabase, - _result: &Option<Arc<[Variance]>>, - _count: u32, - _def: GenericDefId, -) -> salsa::CycleRecoveryAction<Option<Arc<[Variance]>>> { - salsa::CycleRecoveryAction::Iterate -} +// pub(crate) fn variances_of_cycle_fn( +// _db: &dyn HirDatabase, +// _result: &Option<Arc<[Variance]>>, +// _count: u32, +// _def: GenericDefId, +// ) -> salsa::CycleRecoveryAction<Option<Arc<[Variance]>>> { +// salsa::CycleRecoveryAction::Iterate +// } pub(crate) fn variances_of_cycle_initial( db: &dyn HirDatabase, @@ -965,7 +965,7 @@ struct S3<T>(S<T, T>); struct FixedPoint<T, U, V>(&'static FixedPoint<(), T, U>, V); "#, expect![[r#" - FixedPoint[T: covariant, U: covariant, V: covariant] + FixedPoint[T: bivariant, U: bivariant, V: bivariant] "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 5c6f622e6c3..1b2b76999f7 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -2036,7 +2036,7 @@ impl DefWithBody { ) } let mol = &borrowck_result.mutability_of_locals; - for (binding_id, binding_data) in body.bindings.iter() { + for (binding_id, binding_data) in body.bindings() { if binding_data.problems.is_some() { // We should report specific diagnostics for these problems, not `need-mut` and `unused-mut`. continue; @@ -3222,7 +3222,8 @@ impl Macro { } } - pub fn is_asm_or_global_asm(&self, db: &dyn HirDatabase) -> bool { + /// Is this `asm!()`, or a variant of it (e.g. `global_asm!()`)? + pub fn is_asm_like(&self, db: &dyn HirDatabase) -> bool { match self.id { MacroId::Macro2Id(it) => { matches!(it.lookup(db).expander, MacroExpander::BuiltIn(m) if m.is_asm()) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 247bb693983..adba59236a4 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -677,8 +677,7 @@ impl<'db> SemanticsImpl<'db> { pub fn rename_conflicts(&self, to_be_renamed: &Local, new_name: &Name) -> Vec<Local> { let body = self.db.body(to_be_renamed.parent); let resolver = to_be_renamed.parent.resolver(self.db); - let starting_expr = - body.binding_owners.get(&to_be_renamed.binding_id).copied().unwrap_or(body.body_expr); + let starting_expr = body.binding_owner(to_be_renamed.binding_id).unwrap_or(body.body_expr); let mut visitor = RenameConflictsVisitor { body: &body, conflicts: FxHashSet::default(), @@ -1776,7 +1775,7 @@ impl<'db> SemanticsImpl<'db> { pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool { let Some(mac) = self.resolve_macro_call(macro_call) else { return false }; - if mac.is_asm_or_global_asm(self.db) { + if mac.is_asm_like(self.db) { return true; } diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 0662bfddcf8..ecc6e5f3d03 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -242,11 +242,7 @@ impl<'db> SourceAnalyzer<'db> { fn binding_id_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingId> { let pat_id = self.pat_id(&pat.clone().into())?; - if let Pat::Bind { id, .. } = self.store()?.pats[pat_id.as_pat()?] { - Some(id) - } else { - None - } + if let Pat::Bind { id, .. } = self.store()?[pat_id.as_pat()?] { Some(id) } else { None } } pub(crate) fn expr_adjustments(&self, expr: &ast::Expr) -> Option<&[Adjustment]> { @@ -995,7 +991,7 @@ impl<'db> SourceAnalyzer<'db> { let parent_hir_path = path .parent_path() .and_then(|p| collector.lower_path(p, &mut ExprCollector::impl_trait_error_allocator)); - let store = collector.store.finish(); + let (store, _) = collector.store.finish(); // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are // trying to resolve foo::bar. @@ -1204,7 +1200,7 @@ impl<'db> SourceAnalyzer<'db> { let mut collector = ExprCollector::new(db, self.resolver.module(), self.file_id); let hir_path = collector.lower_path(path.clone(), &mut ExprCollector::impl_trait_error_allocator)?; - let store = collector.store.finish(); + let (store, _) = collector.store.finish(); Some(resolve_hir_path_( db, &self.resolver, @@ -1439,9 +1435,11 @@ fn scope_for( ) -> Option<ScopeId> { node.ancestors_with_macros(db) .take_while(|it| { - !ast::Item::can_cast(it.kind()) - || ast::MacroCall::can_cast(it.kind()) - || ast::Use::can_cast(it.kind()) + let kind = it.kind(); + !ast::Item::can_cast(kind) + || ast::MacroCall::can_cast(kind) + || ast::Use::can_cast(kind) + || ast::AsmExpr::can_cast(kind) }) .filter_map(|it| it.map(ast::Expr::cast).transpose()) .filter_map(|it| source_map.node_expr(it.as_ref())?.as_expr()) diff --git a/src/tools/rust-analyzer/crates/hir/src/symbols.rs b/src/tools/rust-analyzer/crates/hir/src/symbols.rs index 756650891d4..dca10193e29 100644 --- a/src/tools/rust-analyzer/crates/hir/src/symbols.rs +++ b/src/tools/rust-analyzer/crates/hir/src/symbols.rs @@ -125,6 +125,13 @@ impl<'a> SymbolCollector<'a> { } ModuleDefId::AdtId(AdtId::EnumId(id)) => { this.push_decl(id, name, false, None); + let enum_name = this.db.enum_signature(id).name.as_str().to_smolstr(); + this.with_container_name(Some(enum_name), |this| { + let variants = id.enum_variants(this.db); + for (variant_id, variant_name, _) in &variants.variants { + this.push_decl(*variant_id, variant_name, true, None); + } + }); } ModuleDefId::AdtId(AdtId::UnionId(id)) => { this.push_decl(id, name, false, None); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs index efcbcef00e9..9126e869b9a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs @@ -1,8 +1,8 @@ use ide_db::defs::{Definition, NameRefClass}; use syntax::{ AstNode, SyntaxNode, - ast::{self, HasName, Name}, - ted, + ast::{self, HasName, Name, syntax_factory::SyntaxFactory}, + syntax_editor::SyntaxEditor, }; use crate::{ @@ -121,34 +121,36 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti // Rename `extracted` with `binding` in `pat`. fn rename_variable(pat: &ast::Pat, extracted: &[Name], binding: ast::Pat) -> SyntaxNode { - let syntax = pat.syntax().clone_for_update(); + let syntax = pat.syntax().clone_subtree(); + let mut editor = SyntaxEditor::new(syntax.clone()); + let make = SyntaxFactory::with_mappings(); let extracted = extracted .iter() - .map(|e| syntax.covering_element(e.syntax().text_range())) + .map(|e| e.syntax().text_range() - pat.syntax().text_range().start()) + .map(|r| syntax.covering_element(r)) .collect::<Vec<_>>(); for extracted_syntax in extracted { // If `extracted` variable is a record field, we should rename it to `binding`, // otherwise we just need to replace `extracted` with `binding`. - if let Some(record_pat_field) = extracted_syntax.ancestors().find_map(ast::RecordPatField::cast) { if let Some(name_ref) = record_pat_field.field_name() { - ted::replace( + editor.replace( record_pat_field.syntax(), - ast::make::record_pat_field( - ast::make::name_ref(&name_ref.text()), - binding.clone(), + make.record_pat_field( + make.name_ref(&name_ref.text()), + binding.clone_for_update(), ) - .syntax() - .clone_for_update(), + .syntax(), ); } } else { - ted::replace(extracted_syntax, binding.clone().syntax().clone_for_update()); + editor.replace(extracted_syntax, binding.syntax().clone_for_update()); } } - syntax + editor.add_mappings(make.finish_with_mappings()); + editor.finish().new_root().clone() } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 32c4ae2e869..8d27574eb2c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -4,7 +4,8 @@ use itertools::Itertools; use syntax::{ SyntaxKind, ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, - match_ast, ted, + match_ast, + syntax_editor::{Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; @@ -97,11 +98,14 @@ fn edit_struct_def( // Note that we don't need to consider macro files in this function because this is // currently not triggered for struct definitions inside macro calls. let tuple_fields = record_fields.fields().filter_map(|f| { - let field = ast::make::tuple_field(f.visibility(), f.ty()?).clone_for_update(); - ted::insert_all( - ted::Position::first_child_of(field.syntax()), + let field = ast::make::tuple_field(f.visibility(), f.ty()?); + let mut editor = SyntaxEditor::new(field.syntax().clone()); + editor.insert_all( + Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), ); + let field_syntax = editor.finish().new_root().clone(); + let field = ast::TupleField::cast(field_syntax)?; Some(field) }); let tuple_fields = ast::make::tuple_field_list(tuple_fields); @@ -1086,8 +1090,7 @@ pub struct $0Foo { } "#, r#" -pub struct Foo(#[my_custom_attr] -u32); +pub struct Foo(#[my_custom_attr]u32); "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs index 79a78ab3698..47233fb399d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs @@ -2,7 +2,7 @@ use ide_db::famous_defs::FamousDefs; use stdx::format_to; use syntax::{ AstNode, - ast::{self, HasGenericParams, HasName, Impl, make}, + ast::{self, HasGenericParams, HasName, HasTypeBounds, Impl, make}, }; use crate::{ @@ -88,20 +88,19 @@ fn generate_trait_impl_text_from_impl( let generic_params = impl_.generic_param_list().map(|generic_params| { let lifetime_params = generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().map(|param| { + let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { // remove defaults since they can't be specified in impls - match param { + let param = match param { ast::TypeOrConstParam::Type(param) => { - let param = param.clone_for_update(); - param.remove_default(); + let param = make::type_param(param.name()?, param.type_bound_list()); ast::GenericParam::TypeParam(param) } ast::TypeOrConstParam::Const(param) => { - let param = param.clone_for_update(); - param.remove_default(); + let param = make::const_param(param.name()?, param.ty()?); ast::GenericParam::ConstParam(param) } - } + }; + Some(param) }); make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs index c7e5e41aac4..20ee9253d37 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs @@ -294,7 +294,7 @@ fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldI let self_expr = make::ext::expr_self(); let lhs = make::expr_field(self_expr, field_name); let rhs = make::expr_path(make::ext::ident_path(field_name)); - let assign_stmt = make::expr_stmt(make::expr_assignment(lhs, rhs)); + let assign_stmt = make::expr_stmt(make::expr_assignment(lhs, rhs).into()); let body = make::block_expr([assign_stmt.into()], None); // Make the setter fn diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs index 2862e6d5afb..14601ca0207 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs @@ -1,14 +1,14 @@ use syntax::{ ast::{self, AstNode, HasName, edit_in_place::Indent, make}, - ted, + syntax_editor::{Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists, utils}; -fn insert_impl(impl_: ast::Impl, nominal: &ast::Adt) { +fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &ast::Adt) { let indent = nominal.indent_level(); - ted::insert_all_raw( - ted::Position::after(nominal.syntax()), + editor.insert_all( + Position::after(nominal.syntax()), vec![ // Add a blank line after the ADT, and indentation for the impl to match the ADT make::tokens::whitespace(&format!("\n\n{indent}")).into(), @@ -51,14 +51,17 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio // Generate the impl let impl_ = utils::generate_impl(&nominal); + let mut editor = edit.make_editor(nominal.syntax()); // Add a tabstop after the left curly brace if let Some(cap) = ctx.config.snippet_cap { if let Some(l_curly) = impl_.assoc_item_list().and_then(|it| it.l_curly_token()) { - edit.add_tabstop_after_token(cap, l_curly); + let tabstop = edit.make_tabstop_after(cap); + editor.add_annotation(l_curly, tabstop); } } - insert_impl(impl_, &edit.make_mut(nominal)); + insert_impl(&mut editor, &impl_, &nominal); + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } @@ -97,18 +100,22 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> // Generate the impl let impl_ = utils::generate_trait_impl_intransitive(&nominal, make::ty_placeholder()); + let mut editor = edit.make_editor(nominal.syntax()); // Make the trait type a placeholder snippet if let Some(cap) = ctx.config.snippet_cap { if let Some(trait_) = impl_.trait_() { - edit.add_placeholder_snippet(cap, trait_); + let placeholder = edit.make_placeholder_snippet(cap); + editor.add_annotation(trait_.syntax(), placeholder); } if let Some(l_curly) = impl_.assoc_item_list().and_then(|it| it.l_curly_token()) { - edit.add_tabstop_after_token(cap, l_curly); + let tabstop = edit.make_tabstop_after(cap); + editor.add_annotation(l_curly, tabstop); } } - insert_impl(impl_, &edit.make_mut(nominal)); + insert_impl(&mut editor, &impl_, &nominal); + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs index bab2ccf3f33..4ddab2cfad0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs @@ -1,6 +1,6 @@ -use ide_db::famous_defs::FamousDefs; +use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait}; use syntax::{ - AstNode, + AstNode, T, ast::{self, edit_in_place::Indent, make}, ted, }; @@ -32,7 +32,7 @@ use crate::{AssistContext, AssistId, Assists}; // // $0impl<T> core::ops::IndexMut<Axis> for [T; 3] { // fn index_mut(&mut self, index: Axis) -> &mut Self::Output { -// &self[index as usize] +// &mut self[index as usize] // } // } // @@ -48,36 +48,34 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> let impl_def = ctx.find_node_at_offset::<ast::Impl>()?.clone_for_update(); let indent = impl_def.indent_level(); - let trait_ = impl_def.trait_()?; - if let ast::Type::PathType(trait_path) = trait_ { - let trait_type = ctx.sema.resolve_trait(&trait_path.path()?)?; - let scope = ctx.sema.scope(trait_path.syntax())?; - if trait_type != FamousDefs(&ctx.sema, scope.krate()).core_convert_Index()? { - return None; - } - } + let ast::Type::PathType(path) = impl_def.trait_()? else { + return None; + }; + let trait_name = path.path()?.segment()?.name_ref()?; + + let scope = ctx.sema.scope(impl_def.trait_()?.syntax())?; + let famous = FamousDefs(&ctx.sema, scope.krate()); + + let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; + let trait_new = get_trait_mut(&trait_, famous)?; // Index -> IndexMut - let index_trait = impl_def - .syntax() - .descendants() - .filter_map(ast::NameRef::cast) - .find(|it| it.text() == "Index")?; - ted::replace( - index_trait.syntax(), - make::path_segment(make::name_ref("IndexMut")).clone_for_update().syntax(), - ); + ted::replace(trait_name.syntax(), make::name_ref(trait_new).clone_for_update().syntax()); // index -> index_mut - let trait_method_name = impl_def + let (trait_method_name, new_trait_method_name) = impl_def .syntax() .descendants() .filter_map(ast::Name::cast) - .find(|it| it.text() == "index")?; - ted::replace(trait_method_name.syntax(), make::name("index_mut").clone_for_update().syntax()); + .find_map(process_method_name)?; + ted::replace( + trait_method_name.syntax(), + make::name(new_trait_method_name).clone_for_update().syntax(), + ); - let type_alias = impl_def.syntax().descendants().find_map(ast::TypeAlias::cast)?; - ted::remove(type_alias.syntax()); + if let Some(type_alias) = impl_def.syntax().descendants().find_map(ast::TypeAlias::cast) { + ted::remove(type_alias.syntax()); + } // &self -> &mut self let mut_self_param = make::mut_self_param(); @@ -87,15 +85,14 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> // &Self::Output -> &mut Self::Output let ret_type = impl_def.syntax().descendants().find_map(ast::RetType::cast)?; - ted::replace( - ret_type.syntax(), - make::ret_type(make::ty("&mut Self::Output")).clone_for_update().syntax(), - ); + let new_ret_type = process_ret_type(&ret_type)?; + ted::replace(ret_type.syntax(), make::ret_type(new_ret_type).clone_for_update().syntax()); let fn_ = impl_def.assoc_item_list()?.assoc_items().find_map(|it| match it { ast::AssocItem::Fn(f) => Some(f), _ => None, })?; + let _ = process_ref_mut(&fn_); let assoc_list = make::assoc_item_list().clone_for_update(); ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax()); @@ -104,7 +101,7 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> let target = impl_def.syntax().text_range(); acc.add( AssistId::generate("generate_mut_trait_impl"), - "Generate `IndexMut` impl from this `Index` trait", + format!("Generate `{trait_new}` impl from this `{trait_name}` trait"), target, |edit| { edit.insert(target.start(), format!("$0{impl_def}\n\n{indent}")); @@ -112,6 +109,52 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> ) } +fn process_ref_mut(fn_: &ast::Fn) -> Option<()> { + let expr = fn_.body()?.tail_expr()?; + match &expr { + ast::Expr::RefExpr(ref_expr) if ref_expr.mut_token().is_none() => { + ted::insert_all_raw( + ted::Position::after(ref_expr.amp_token()?), + vec![make::token(T![mut]).into(), make::tokens::whitespace(" ").into()], + ); + } + _ => {} + } + None +} + +fn get_trait_mut(apply_trait: &hir::Trait, famous: FamousDefs<'_, '_>) -> Option<&'static str> { + let trait_ = Some(apply_trait); + if trait_ == famous.core_convert_Index().as_ref() { + return Some("IndexMut"); + } + if trait_ == famous.core_convert_AsRef().as_ref() { + return Some("AsMut"); + } + if trait_ == famous.core_borrow_Borrow().as_ref() { + return Some("BorrowMut"); + } + None +} + +fn process_method_name(name: ast::Name) -> Option<(ast::Name, &'static str)> { + let new_name = match &*name.text() { + "index" => "index_mut", + "as_ref" => "as_mut", + "borrow" => "borrow_mut", + _ => return None, + }; + Some((name, new_name)) +} + +fn process_ret_type(ref_ty: &ast::RetType) -> Option<ast::Type> { + let ty = ref_ty.ty()?; + let ast::Type::RefType(ref_type) = ty else { + return None; + }; + Some(make::ty_ref(ref_type.ty()?, true)) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -139,7 +182,7 @@ pub enum Axis { X = 0, Y = 1, Z = 2 } $0impl<T> core::ops::IndexMut<Axis> for [T; 3] { fn index_mut(&mut self, index: Axis) -> &mut Self::Output { - &self[index as usize] + &mut self[index as usize] } } @@ -188,6 +231,35 @@ impl<T> core::ops::Index<Axis> for [T; 3] where T: Copy { } "#, ); + + check_assist( + generate_mut_trait_impl, + r#" +//- minicore: as_ref +struct Foo(i32); + +impl core::convert::AsRef$0<i32> for Foo { + fn as_ref(&self) -> &i32 { + &self.0 + } +} +"#, + r#" +struct Foo(i32); + +$0impl core::convert::AsMut<i32> for Foo { + fn as_mut(&mut self) -> &mut i32 { + &mut self.0 + } +} + +impl core::convert::AsRef<i32> for Foo { + fn as_ref(&self) -> &i32 { + &self.0 + } +} +"#, + ); } #[test] @@ -287,5 +359,13 @@ pub trait Index<Idx: ?Sized> {} impl<T> Index$0<i32> for [T; 3] {} "#, ); + check_assist_not_applicable( + generate_mut_trait_impl, + r#" +pub trait AsRef<T: ?Sized> {} + +impl AsRef$0<i32> for [T; 3] {} +"#, + ); } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs index 4837f92f934..51c2f65e025 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs @@ -1,5 +1,6 @@ use ide_db::{ - imports::import_assets::item_for_path_search, use_trivial_constructor::use_trivial_constructor, + imports::import_assets::item_for_path_search, syntax_helpers::suggest_name::NameGenerator, + use_trivial_constructor::use_trivial_constructor, }; use syntax::{ ast::{self, AstNode, HasName, HasVisibility, StructKind, edit_in_place::Indent, make}, @@ -35,10 +36,30 @@ use crate::{ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let strukt = ctx.find_node_at_offset::<ast::Struct>()?; - // We want to only apply this to non-union structs with named fields let field_list = match strukt.kind() { - StructKind::Record(named) => named, - _ => return None, + StructKind::Record(named) => { + named.fields().filter_map(|f| Some((f.name()?, f.ty()?))).collect::<Vec<_>>() + } + StructKind::Tuple(tuple) => { + let mut name_generator = NameGenerator::default(); + tuple + .fields() + .enumerate() + .filter_map(|(i, f)| { + let ty = f.ty()?; + let name = match name_generator.for_type( + &ctx.sema.resolve_type(&ty)?, + ctx.db(), + ctx.edition(), + ) { + Some(name) => name, + None => name_generator.suggest_name(&format!("_{i}")), + }; + Some((make::name(name.as_str()), f.ty()?)) + }) + .collect::<Vec<_>>() + } + StructKind::Unit => return None, }; // Return early if we've found an existing new fn @@ -50,11 +71,9 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let target = strukt.syntax().text_range(); acc.add(AssistId::generate("generate_new"), "Generate `new`", target, |builder| { let trivial_constructors = field_list - .fields() - .map(|f| { - let name = f.name()?; - - let ty = ctx.sema.resolve_type(&f.ty()?)?; + .iter() + .map(|(name, ty)| { + let ty = ctx.sema.resolve_type(ty)?; let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?)); @@ -73,34 +92,44 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option edition, )?; - Some(make::record_expr_field(make::name_ref(&name.text()), Some(expr))) + Some((make::name_ref(&name.text()), Some(expr))) }) .collect::<Vec<_>>(); - let params = field_list.fields().enumerate().filter_map(|(i, f)| { + let params = field_list.iter().enumerate().filter_map(|(i, (name, ty))| { if trivial_constructors[i].is_none() { - let name = f.name()?; - let ty = f.ty()?; - - Some(make::param(make::ident_pat(false, false, name).into(), ty)) + Some(make::param(make::ident_pat(false, false, name.clone()).into(), ty.clone())) } else { None } }); let params = make::param_list(None, params); - let fields = field_list.fields().enumerate().filter_map(|(i, f)| { - let constructor = trivial_constructors[i].clone(); - if constructor.is_some() { + let fields = field_list.iter().enumerate().map(|(i, (name, _))| { + if let Some(constructor) = trivial_constructors[i].clone() { constructor } else { - Some(make::record_expr_field(make::name_ref(&f.name()?.text()), None)) + (make::name_ref(&name.text()), None) } }); - let fields = make::record_expr_field_list(fields); - let record_expr = make::record_expr(make::ext::ident_path("Self"), fields); - let body = make::block_expr(None, Some(record_expr.into())); + let tail_expr: ast::Expr = match strukt.kind() { + StructKind::Record(_) => { + let fields = fields.map(|(name, expr)| make::record_expr_field(name, expr)); + let fields = make::record_expr_field_list(fields); + make::record_expr(make::ext::ident_path("Self"), fields).into() + } + StructKind::Tuple(_) => { + let args = fields.map(|(arg, expr)| { + let arg = || make::expr_path(make::path_unqualified(make::path_segment(arg))); + expr.unwrap_or_else(arg) + }); + let arg_list = make::arg_list(args); + make::expr_call(make::expr_path(make::ext::ident_path("Self")), arg_list).into() + } + StructKind::Unit => unreachable!(), + }; + let body = make::block_expr(None, tail_expr.into()); let ret_type = make::ret_type(make::ty_path(make::ext::ident_path("Self"))); @@ -120,8 +149,35 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option .clone_for_update(); fn_.indent(1.into()); - // Add a tabstop before the name if let Some(cap) = ctx.config.snippet_cap { + match strukt.kind() { + StructKind::Tuple(_) => { + let struct_args = fn_ + .body() + .unwrap() + .syntax() + .descendants() + .filter(|it| syntax::ast::ArgList::can_cast(it.kind())) + .flat_map(|args| args.children()) + .filter(|it| syntax::ast::PathExpr::can_cast(it.kind())) + .enumerate() + .filter_map(|(i, node)| { + if trivial_constructors[i].is_none() { Some(node) } else { None } + }); + if let Some(fn_params) = fn_.param_list() { + for (struct_arg, fn_param) in struct_args.zip(fn_params.params()) { + if let Some(fn_pat) = fn_param.pat() { + let fn_pat = fn_pat.syntax().clone(); + builder + .add_placeholder_snippet_group(cap, vec![struct_arg, fn_pat]); + } + } + } + } + _ => {} + } + + // Add a tabstop before the name if let Some(name) = fn_.name() { builder.add_tabstop_before(cap, name); } @@ -157,7 +213,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option } #[cfg(test)] -mod tests { +mod record_tests { use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use super::*; @@ -695,3 +751,308 @@ impl<T> Source<T> { ); } } + +#[cfg(test)] +mod tuple_tests { + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; + + use super::*; + + #[test] + fn test_generate_new_with_zst_fields() { + check_assist( + generate_new, + r#" +struct Empty; + +struct Foo(Empty$0); +"#, + r#" +struct Empty; + +struct Foo(Empty); + +impl Foo { + fn $0new() -> Self { + Self(Empty) + } +} +"#, + ); + check_assist( + generate_new, + r#" +struct Empty; + +struct Foo(String, Empty$0); +"#, + r#" +struct Empty; + +struct Foo(String, Empty); + +impl Foo { + fn $0new(${1:_0}: String) -> Self { + Self(${1:_0}, Empty) + } +} +"#, + ); + check_assist( + generate_new, + r#" +enum Empty { Bar } + +struct Foo(Empty$0); +"#, + r#" +enum Empty { Bar } + +struct Foo(Empty); + +impl Foo { + fn $0new() -> Self { + Self(Empty::Bar) + } +} +"#, + ); + + // make sure the assist only works on unit variants + check_assist( + generate_new, + r#" +struct Empty {} + +struct Foo(Empty$0); +"#, + r#" +struct Empty {} + +struct Foo(Empty); + +impl Foo { + fn $0new(${1:empty}: Empty) -> Self { + Self(${1:empty}) + } +} +"#, + ); + check_assist( + generate_new, + r#" +enum Empty { Bar {} } + +struct Foo(Empty$0); +"#, + r#" +enum Empty { Bar {} } + +struct Foo(Empty); + +impl Foo { + fn $0new(${1:empty}: Empty) -> Self { + Self(${1:empty}) + } +} +"#, + ); + } + + #[test] + fn test_generate_new() { + check_assist( + generate_new, + r#" +struct Foo($0); +"#, + r#" +struct Foo(); + +impl Foo { + fn $0new() -> Self { + Self() + } +} +"#, + ); + check_assist( + generate_new, + r#" +struct Foo<T: Clone>($0); +"#, + r#" +struct Foo<T: Clone>(); + +impl<T: Clone> Foo<T> { + fn $0new() -> Self { + Self() + } +} +"#, + ); + check_assist( + generate_new, + r#" +struct Foo<'a, T: Foo<'a>>($0); +"#, + r#" +struct Foo<'a, T: Foo<'a>>(); + +impl<'a, T: Foo<'a>> Foo<'a, T> { + fn $0new() -> Self { + Self() + } +} +"#, + ); + check_assist( + generate_new, + r#" +struct Foo(String$0); +"#, + r#" +struct Foo(String); + +impl Foo { + fn $0new(${1:_0}: String) -> Self { + Self(${1:_0}) + } +} +"#, + ); + check_assist( + generate_new, + r#" +struct Vec<T> { }; +struct Foo(String, Vec<i32>$0); +"#, + r#" +struct Vec<T> { }; +struct Foo(String, Vec<i32>); + +impl Foo { + fn $0new(${1:_0}: String, ${2:items}: Vec<i32>) -> Self { + Self(${1:_0}, ${2:items}) + } +} +"#, + ); + } + + #[test] + fn check_that_visibility_modifiers_dont_get_brought_in() { + check_assist( + generate_new, + r#" +struct Vec<T> { }; +struct Foo(pub String, pub Vec<i32>$0); +"#, + r#" +struct Vec<T> { }; +struct Foo(pub String, pub Vec<i32>); + +impl Foo { + fn $0new(${1:_0}: String, ${2:items}: Vec<i32>) -> Self { + Self(${1:_0}, ${2:items}) + } +} +"#, + ); + } + + #[test] + fn generate_new_not_applicable_if_fn_exists() { + check_assist_not_applicable( + generate_new, + r#" +struct Foo($0); + +impl Foo { + fn new() -> Self { + Self + } +} +"#, + ); + + check_assist_not_applicable( + generate_new, + r#" +struct Foo($0); + +impl Foo { + fn New() -> Self { + Self + } +} +"#, + ); + } + + #[test] + fn generate_new_target() { + check_assist_target( + generate_new, + r#" +struct SomeThingIrrelevant; +/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>>($0); +struct EvenMoreIrrelevant; +"#, + "/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>>();", + ); + } + + #[test] + fn test_unrelated_new() { + check_assist( + generate_new, + r#" +pub struct AstId<N: AstNode> { + file_id: HirFileId, + file_ast_id: FileAstId<N>, +} + +impl<N: AstNode> AstId<N> { + pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { + AstId { file_id, file_ast_id } + } +} + +pub struct Source<T>(pub HirFileId,$0 pub T); + +impl<T> Source<T> { + pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { + Source(self.file_id, f(self.ast)) + } +} +"#, + r#" +pub struct AstId<N: AstNode> { + file_id: HirFileId, + file_ast_id: FileAstId<N>, +} + +impl<N: AstNode> AstId<N> { + pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { + AstId { file_id, file_ast_id } + } +} + +pub struct Source<T>(pub HirFileId, pub T); + +impl<T> Source<T> { + pub fn $0new(${1:_0}: HirFileId, ${2:_1}: T) -> Self { + Self(${1:_0}, ${2:_1}) + } + + pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { + Source(self.file_id, f(self.ast)) + } +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs new file mode 100644 index 00000000000..4e95ceb2e85 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs @@ -0,0 +1,1000 @@ +use ast::make; +use hir::{HasCrate, ModuleDef, Semantics}; +use ide_db::{ + RootDatabase, famous_defs::FamousDefs, helpers::mod_path_to_ast, + imports::import_assets::item_for_path_search, use_trivial_constructor::use_trivial_constructor, +}; +use syntax::{ + TokenText, + ast::{self, AstNode, HasGenericParams, HasName, edit, edit_in_place::Indent}, +}; + +use crate::{ + AssistId, + assist_context::{AssistContext, Assists}, + utils::add_cfg_attrs_to, +}; + +// Assist: generate_single_field_struct_from +// +// Implement From for a single field structure, ignore trivial types. +// +// ``` +// # //- minicore: from, phantom_data +// use core::marker::PhantomData; +// struct $0Foo<T> { +// id: i32, +// _phantom_data: PhantomData<T>, +// } +// ``` +// -> +// ``` +// use core::marker::PhantomData; +// struct Foo<T> { +// id: i32, +// _phantom_data: PhantomData<T>, +// } +// +// impl<T> From<i32> for Foo<T> { +// fn from(id: i32) -> Self { +// Self { id, _phantom_data: PhantomData } +// } +// } +// ``` +pub(crate) fn generate_single_field_struct_from( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let strukt_name = ctx.find_node_at_offset::<ast::Name>()?; + let adt = ast::Adt::cast(strukt_name.syntax().parent()?)?; + let ast::Adt::Struct(strukt) = adt else { + return None; + }; + + let sema = &ctx.sema; + let (names, types) = get_fields(&strukt)?; + + let module = sema.scope(strukt.syntax())?.module(); + let constructors = make_constructors(ctx, module, &types); + + if constructors.iter().filter(|expr| expr.is_none()).count() != 1 { + return None; + } + let main_field_i = constructors.iter().position(Option::is_none)?; + if from_impl_exists(&strukt, main_field_i, &ctx.sema).is_some() { + return None; + } + + let main_field_name = + names.as_ref().map_or(TokenText::borrowed("value"), |names| names[main_field_i].text()); + let main_field_ty = types[main_field_i].clone(); + + acc.add( + AssistId::generate("generate_single_field_struct_from"), + "Generate single field `From`", + strukt.syntax().text_range(), + |builder| { + let indent = strukt.indent_level(); + let ty_where_clause = strukt.where_clause(); + let type_gen_params = strukt.generic_param_list(); + let type_gen_args = type_gen_params.as_ref().map(|params| params.to_generic_args()); + let trait_gen_args = Some(make::generic_arg_list([ast::GenericArg::TypeArg( + make::type_arg(main_field_ty.clone()), + )])); + + let ty = make::ty(&strukt_name.text()); + + let constructor = + make_adt_constructor(names.as_deref(), constructors, &main_field_name); + let body = make::block_expr([], Some(constructor)); + + let fn_ = make::fn_( + None, + make::name("from"), + None, + None, + make::param_list( + None, + [make::param( + make::path_pat(make::path_from_text(&main_field_name)), + main_field_ty, + )], + ), + body, + Some(make::ret_type(make::ty("Self"))), + false, + false, + false, + false, + ) + .clone_for_update(); + + fn_.indent(1.into()); + + let impl_ = make::impl_trait( + false, + None, + trait_gen_args, + type_gen_params, + type_gen_args, + false, + make::ty("From"), + ty.clone(), + None, + ty_where_clause.map(|wc| edit::AstNodeEdit::reset_indent(&wc)), + None, + ) + .clone_for_update(); + + impl_.get_or_create_assoc_item_list().add_item(fn_.into()); + + add_cfg_attrs_to(&strukt, &impl_); + + impl_.reindent_to(indent); + + builder.insert(strukt.syntax().text_range().end(), format!("\n\n{indent}{impl_}")); + }, + ) +} + +fn make_adt_constructor( + names: Option<&[ast::Name]>, + constructors: Vec<Option<ast::Expr>>, + main_field_name: &TokenText<'_>, +) -> ast::Expr { + if let Some(names) = names { + let fields = make::record_expr_field_list(names.iter().zip(constructors).map( + |(name, initializer)| { + make::record_expr_field(make::name_ref(&name.text()), initializer) + }, + )); + make::record_expr(make::path_from_text("Self"), fields).into() + } else { + let arg_list = make::arg_list(constructors.into_iter().map(|expr| { + expr.unwrap_or_else(|| make::expr_path(make::path_from_text(main_field_name))) + })); + make::expr_call(make::expr_path(make::path_from_text("Self")), arg_list).into() + } +} + +fn make_constructors( + ctx: &AssistContext<'_>, + module: hir::Module, + types: &[ast::Type], +) -> Vec<Option<ast::Expr>> { + let (db, sema) = (ctx.db(), &ctx.sema); + types + .iter() + .map(|ty| { + let ty = sema.resolve_type(ty)?; + if ty.is_unit() { + return Some(make::expr_tuple([]).into()); + } + let item_in_ns = ModuleDef::Adt(ty.as_adt()?).into(); + let edition = module.krate().edition(db); + + let ty_path = module.find_path( + db, + item_for_path_search(db, item_in_ns)?, + ctx.config.import_path_config(), + )?; + + use_trivial_constructor(db, mod_path_to_ast(&ty_path, edition), &ty, edition) + }) + .collect() +} + +fn get_fields(strukt: &ast::Struct) -> Option<(Option<Vec<ast::Name>>, Vec<ast::Type>)> { + Some(match strukt.kind() { + ast::StructKind::Unit => return None, + ast::StructKind::Record(fields) => { + let names = fields.fields().map(|field| field.name()).collect::<Option<_>>()?; + let types = fields.fields().map(|field| field.ty()).collect::<Option<_>>()?; + (Some(names), types) + } + ast::StructKind::Tuple(fields) => { + (None, fields.fields().map(|field| field.ty()).collect::<Option<_>>()?) + } + }) +} + +fn from_impl_exists( + strukt: &ast::Struct, + main_field_i: usize, + sema: &Semantics<'_, RootDatabase>, +) -> Option<()> { + let db = sema.db; + let strukt = sema.to_def(strukt)?; + let krate = strukt.krate(db); + let from_trait = FamousDefs(sema, krate).core_convert_From()?; + let ty = strukt.fields(db).get(main_field_i)?.ty(db); + + strukt.ty(db).impls_trait(db, from_trait, &[ty]).then_some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::generate_single_field_struct_from; + + #[test] + fn works() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo { + foo: i32, + } + "#, + r#" + struct Foo { + foo: i32, + } + + impl From<i32> for Foo { + fn from(foo: i32) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from, phantom_data + struct $0Foo { + b1: (), + b2: core::marker::PhantomData, + foo: i32, + a1: (), + a2: core::marker::PhantomData, + } + "#, + r#" + struct Foo { + b1: (), + b2: core::marker::PhantomData, + foo: i32, + a1: (), + a2: core::marker::PhantomData, + } + + impl From<i32> for Foo { + fn from(foo: i32) -> Self { + Self { b1: (), b2: core::marker::PhantomData, foo, a1: (), a2: core::marker::PhantomData } + } + } + "#, + ); + } + + #[test] + fn cfgs() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + #[cfg(feature = "foo")] + #[cfg(test)] + struct $0Foo { + foo: i32, + } + "#, + r#" + #[cfg(feature = "foo")] + #[cfg(test)] + struct Foo { + foo: i32, + } + + #[cfg(feature = "foo")] + #[cfg(test)] + impl From<i32> for Foo { + fn from(foo: i32) -> Self { + Self { foo } + } + } + "#, + ); + } + + #[test] + fn indent() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + mod foo { + struct $0Foo { + foo: i32, + } + } + "#, + r#" + mod foo { + struct Foo { + foo: i32, + } + + impl From<i32> for Foo { + fn from(foo: i32) -> Self { + Self { foo } + } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + mod foo { + mod bar { + struct $0Foo { + foo: i32, + } + } + } + "#, + r#" + mod foo { + mod bar { + struct Foo { + foo: i32, + } + + impl From<i32> for Foo { + fn from(foo: i32) -> Self { + Self { foo } + } + } + } + } + "#, + ); + } + + #[test] + fn where_clause_indent() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + mod foo { + mod bar { + trait Trait {} + struct $0Foo<T> + where + T: Trait, + { + foo: T, + } + } + } + "#, + r#" + mod foo { + mod bar { + trait Trait {} + struct Foo<T> + where + T: Trait, + { + foo: T, + } + + impl<T> From<T> for Foo<T> + where + T: Trait, + { + fn from(foo: T) -> Self { + Self { foo } + } + } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + mod foo { + mod bar { + trait Trait<const B: bool> {} + struct $0Foo<T> + where + T: Trait<{ + true + }> + { + foo: T, + } + } + } + "#, + r#" + mod foo { + mod bar { + trait Trait<const B: bool> {} + struct Foo<T> + where + T: Trait<{ + true + }> + { + foo: T, + } + + impl<T> From<T> for Foo<T> + where + T: Trait<{ + true + }> + { + fn from(foo: T) -> Self { + Self { foo } + } + } + } + } + "#, + ); + } + + #[test] + fn generics() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T> { + foo: T, + } + "#, + r#" + struct Foo<T> { + foo: T, + } + + impl<T> From<T> for Foo<T> { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> { + foo: T, + } + "#, + r#" + struct Foo<T: Send> { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> where T: Sync,{ + foo: T, + } + "#, + r#" + struct Foo<T: Send> where T: Sync,{ + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where T: Sync, + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> where T: Sync { + foo: T, + } + "#, + r#" + struct Foo<T: Send> where T: Sync { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where T: Sync + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> where T: Sync, Self: Send { + foo: T, + } + "#, + r#" + struct Foo<T: Send> where T: Sync, Self: Send { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where T: Sync, Self: Send + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> + where T: Sync, Self: Send + { + foo: T, + } + "#, + r#" + struct Foo<T: Send> + where T: Sync, Self: Send + { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where T: Sync, Self: Send + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> + where T: Sync, Self: Send, + { + foo: T, + } + "#, + r#" + struct Foo<T: Send> + where T: Sync, Self: Send, + { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where T: Sync, Self: Send, + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> + where T: Sync, + Self: Send, + { + foo: T, + } + "#, + r#" + struct Foo<T: Send> + where T: Sync, + Self: Send, + { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where T: Sync, + Self: Send, + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send> + where + T: Sync, + Self: Send, + { + foo: T, + } + "#, + r#" + struct Foo<T: Send> + where + T: Sync, + Self: Send, + { + foo: T, + } + + impl<T: Send> From<T> for Foo<T> + where + T: Sync, + Self: Send, + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T: Send + Sync> + where + T: Sync, + Self: Send, + { + foo: T, + } + "#, + r#" + struct Foo<T: Send + Sync> + where + T: Sync, + Self: Send, + { + foo: T, + } + + impl<T: Send + Sync> From<T> for Foo<T> + where + T: Sync, + Self: Send, + { + fn from(foo: T) -> Self { + Self { foo } + } + } + "#, + ); + } + + #[test] + fn tuple() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32); + "#, + r#" + struct Foo(i32); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(value) + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T>(T); + "#, + r#" + struct Foo<T>(T); + + impl<T> From<T> for Foo<T> { + fn from(value: T) -> Self { + Self(value) + } + } + "#, + ); + } + + #[test] + fn trivial() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from, phantom_data + use core::marker::PhantomData; + struct $0Foo(i32, PhantomData<i32>); + "#, + r#" + use core::marker::PhantomData; + struct Foo(i32, PhantomData<i32>); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(value, PhantomData) + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from, phantom_data + use core::marker::PhantomData; + struct $0Foo(i32, PhantomData<()>); + "#, + r#" + use core::marker::PhantomData; + struct Foo(i32, PhantomData<()>); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(value, PhantomData) + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from, phantom_data + use core::marker::PhantomData; + struct $0Foo(PhantomData<()>, i32, PhantomData<()>); + "#, + r#" + use core::marker::PhantomData; + struct Foo(PhantomData<()>, i32, PhantomData<()>); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(PhantomData, value, PhantomData) + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from, phantom_data + use core::marker::PhantomData; + struct $0Foo<T>(PhantomData<T>, i32, PhantomData<()>); + "#, + r#" + use core::marker::PhantomData; + struct Foo<T>(PhantomData<T>, i32, PhantomData<()>); + + impl<T> From<i32> for Foo<T> { + fn from(value: i32) -> Self { + Self(PhantomData, value, PhantomData) + } + } + "#, + ); + } + + #[test] + fn unit() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32, ()); + "#, + r#" + struct Foo(i32, ()); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(value, ()) + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo((), i32, ()); + "#, + r#" + struct Foo((), i32, ()); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self((), value, ()) + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo((), (), i32, ()); + "#, + r#" + struct Foo((), (), i32, ()); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self((), (), value, ()) + } + } + "#, + ); + } + + #[test] + fn invalid_multiple_main_field() { + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32, i32); + "#, + ); + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T>(i32, T); + "#, + ); + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T>(T, T); + "#, + ); + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo<T> { foo: T, bar: i32 } + "#, + ); + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo { foo: i32, bar: i64 } + "#, + ); + } + + #[test] + fn exists_other_from() { + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32); + + impl From<&i32> for Foo { + fn from(value: &i32) -> Self { + todo!() + } + } + "#, + r#" + struct Foo(i32); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(value) + } + } + + impl From<&i32> for Foo { + fn from(value: &i32) -> Self { + todo!() + } + } + "#, + ); + check_assist( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32); + + type X = i32; + + impl From<&X> for Foo { + fn from(value: &X) -> Self { + todo!() + } + } + "#, + r#" + struct Foo(i32); + + impl From<i32> for Foo { + fn from(value: i32) -> Self { + Self(value) + } + } + + type X = i32; + + impl From<&X> for Foo { + fn from(value: &X) -> Self { + todo!() + } + } + "#, + ); + } + + #[test] + fn exists_from() { + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32); + + impl From<i32> for Foo { + fn from(_: i32) -> Self { + todo!() + } + } + "#, + ); + check_assist_not_applicable( + generate_single_field_struct_from, + r#" + //- minicore: from + struct $0Foo(i32); + + type X = i32; + + impl From<X> for Foo { + fn from(_: X) -> Self { + todo!() + } + } + "#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs index 5f626d29571..1b0c3139353 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs @@ -1,7 +1,8 @@ use syntax::{ AstNode, - ast::{self, make}, - ted, + algo::find_node_at_range, + ast::{self, syntax_factory::SyntaxFactory}, + syntax_editor::SyntaxEditor, }; use crate::{ @@ -66,33 +67,51 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext<'_>) -> return None; } } - + let target = tgt.syntax().text_range(); + + let edit_tgt = tgt.syntax().clone_subtree(); + let assignments: Vec<_> = collector + .assignments + .into_iter() + .filter_map(|(stmt, rhs)| { + Some(( + find_node_at_range::<ast::BinExpr>( + &edit_tgt, + stmt.syntax().text_range() - target.start(), + )?, + find_node_at_range::<ast::Expr>( + &edit_tgt, + rhs.syntax().text_range() - target.start(), + )?, + )) + }) + .collect(); + + let mut editor = SyntaxEditor::new(edit_tgt); + for (stmt, rhs) in assignments { + let mut stmt = stmt.syntax().clone(); + if let Some(parent) = stmt.parent() { + if ast::ExprStmt::cast(parent.clone()).is_some() { + stmt = parent.clone(); + } + } + editor.replace(stmt, rhs.syntax()); + } + let new_tgt_root = editor.finish().new_root().clone(); + let new_tgt = ast::Expr::cast(new_tgt_root)?; acc.add( AssistId::refactor_extract("pull_assignment_up"), "Pull assignment up", - tgt.syntax().text_range(), + target, move |edit| { - let assignments: Vec<_> = collector - .assignments - .into_iter() - .map(|(stmt, rhs)| (edit.make_mut(stmt), rhs.clone_for_update())) - .collect(); - - let tgt = edit.make_mut(tgt); - - for (stmt, rhs) in assignments { - let mut stmt = stmt.syntax().clone(); - if let Some(parent) = stmt.parent() { - if ast::ExprStmt::cast(parent.clone()).is_some() { - stmt = parent.clone(); - } - } - ted::replace(stmt, rhs.syntax()); - } - let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone()); - let assign_stmt = make::expr_stmt(assign_expr); - - ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update()); + let make = SyntaxFactory::with_mappings(); + let mut editor = edit.make_editor(tgt.syntax()); + let assign_expr = make.expr_assignment(collector.common_lhs, new_tgt.clone()); + let assign_stmt = make.expr_stmt(assign_expr.into()); + + editor.replace(tgt.syntax(), assign_stmt.syntax()); + editor.add_mappings(make.finish_with_mappings()); + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index 52ace03f3cf..9356d02706c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -1,8 +1,9 @@ use itertools::Itertools; use syntax::{ - Edition, NodeOrToken, SyntaxElement, T, TextRange, TextSize, - ast::{self, AstNode, AstToken, make}, - match_ast, ted, + Edition, NodeOrToken, SyntaxNode, SyntaxToken, T, + ast::{self, AstNode, make}, + match_ast, + syntax_editor::{Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists}; @@ -40,21 +41,23 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let replacements = macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>(); - - acc.add( - AssistId::quick_fix("remove_dbg"), - "Remove dbg!()", - replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range))?, - |builder| { - for (range, expr) in replacements { - if let Some(expr) = expr { - builder.replace(range, expr.to_string()); - } else { - builder.delete(range); - } + let target = replacements + .iter() + .flat_map(|(node_or_token, _)| node_or_token.iter()) + .map(|t| t.text_range()) + .reduce(|acc, range| acc.cover(range))?; + acc.add(AssistId::quick_fix("remove_dbg"), "Remove dbg!()", target, |builder| { + let mut editor = builder.make_editor(ctx.source_file().syntax()); + for (range, expr) in replacements { + if let Some(expr) = expr { + editor.insert(Position::before(range[0].clone()), expr.syntax().clone_for_update()); + } + for node_or_token in range { + editor.delete(node_or_token); } - }, - ) + } + builder.add_file_edits(ctx.vfs_file_id(), editor); + }) } /// Returns `None` when either @@ -63,7 +66,9 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( /// - (`macro_expr` has no parent - is that possible?) /// /// Returns `Some(_, None)` when the macro call should just be removed. -fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Option<ast::Expr>)> { +fn compute_dbg_replacement( + macro_expr: ast::MacroExpr, +) -> Option<(Vec<NodeOrToken<SyntaxNode, SyntaxToken>>, Option<ast::Expr>)> { let macro_call = macro_expr.macro_call()?; let tt = macro_call.token_tree()?; let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?); @@ -88,22 +93,22 @@ fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Opt match_ast! { match parent { ast::StmtList(_) => { - let range = macro_expr.syntax().text_range(); - let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) { - Some(start) => range.cover_offset(start), - None => range, - }; - (range, None) + let mut replace = vec![macro_expr.syntax().clone().into()]; + if let Some(prev_sibling) = macro_expr.syntax().prev_sibling_or_token() + && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE { + replace.push(prev_sibling); + } + (replace, None) }, ast::ExprStmt(it) => { - let range = it.syntax().text_range(); - let range = match whitespace_start(it.syntax().prev_sibling_or_token()) { - Some(start) => range.cover_offset(start), - None => range, - }; - (range, None) + let mut replace = vec![it.syntax().clone().into()]; + if let Some(prev_sibling) = it.syntax().prev_sibling_or_token() + && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE { + replace.push(prev_sibling); + } + (replace, None) }, - _ => (macro_call.syntax().text_range(), Some(make::ext::expr_unit())), + _ => (vec![macro_call.syntax().clone().into()], Some(make::ext::expr_unit())), } } } @@ -147,13 +152,13 @@ fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Opt }; let expr = replace_nested_dbgs(expr.clone()); let expr = if wrap { make::expr_paren(expr).into() } else { expr.clone_subtree() }; - (macro_call.syntax().text_range(), Some(expr)) + (vec![macro_call.syntax().clone().into()], Some(expr)) } // dbg!(expr0, expr1, ...) exprs => { let exprs = exprs.iter().cloned().map(replace_nested_dbgs); let expr = make::expr_tuple(exprs); - (macro_call.syntax().text_range(), Some(expr.into())) + (vec![macro_call.syntax().clone().into()], Some(expr.into())) } }) } @@ -178,8 +183,8 @@ fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { return replaced; } - let expanded = expanded.clone_for_update(); - + let expanded = expanded.clone_subtree(); + let mut editor = SyntaxEditor::new(expanded.syntax().clone()); // We need to collect to avoid mutation during traversal. let macro_exprs: Vec<_> = expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); @@ -191,17 +196,13 @@ fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { }; if let Some(expr) = expr_opt { - ted::replace(mac.syntax(), expr.syntax().clone_for_update()); + editor.replace(mac.syntax(), expr.syntax().clone_for_update()); } else { - ted::remove(mac.syntax()); + editor.delete(mac.syntax()); } } - - expanded -} - -fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> { - Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) + let expanded_syntax = editor.finish().new_root().clone(); + ast::Expr::cast(expanded_syntax).unwrap() } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs index 62914ee7f38..5ef8ba46b9e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs @@ -64,13 +64,12 @@ pub(crate) fn replace_is_method_with_if_let_method( let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat.into()]); let let_expr = make.expr_let(pat.into(), receiver); - if let Some(cap) = ctx.config.snippet_cap { - if let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat() { - if let Some(first_var) = pat.fields().next() { - let placeholder = edit.make_placeholder_snippet(cap); - editor.add_annotation(first_var.syntax(), placeholder); - } - } + if let Some(cap) = ctx.config.snippet_cap + && let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat() + && let Some(first_var) = pat.fields().next() + { + let placeholder = edit.make_placeholder_snippet(cap); + editor.add_annotation(first_var.syntax(), placeholder); } editor.replace(call_expr.syntax(), let_expr.syntax()); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index c2604432032..cde0d875e0d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -172,6 +172,7 @@ mod handlers { mod generate_is_empty_from_len; mod generate_mut_trait_impl; mod generate_new; + mod generate_single_field_struct_from; mod generate_trait_from_impl; mod inline_call; mod inline_const_as_literal; @@ -305,6 +306,7 @@ mod handlers { generate_mut_trait_impl::generate_mut_trait_impl, generate_new::generate_new, generate_trait_from_impl::generate_trait_from_impl, + generate_single_field_struct_from::generate_single_field_struct_from, inline_call::inline_call, inline_call::inline_into_callers, inline_const_as_literal::inline_const_as_literal, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 72f7195cbd7..fc1c6928ff3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -1933,7 +1933,7 @@ pub enum Axis { X = 0, Y = 1, Z = 2 } $0impl<T> core::ops::IndexMut<Axis> for [T; 3] { fn index_mut(&mut self, index: Axis) -> &mut Self::Output { - &self[index as usize] + &mut self[index as usize] } } @@ -1995,6 +1995,34 @@ impl Person { } #[test] +fn doctest_generate_single_field_struct_from() { + check_doc_test( + "generate_single_field_struct_from", + r#####" +//- minicore: from, phantom_data +use core::marker::PhantomData; +struct $0Foo<T> { + id: i32, + _phantom_data: PhantomData<T>, +} +"#####, + r#####" +use core::marker::PhantomData; +struct Foo<T> { + id: i32, + _phantom_data: PhantomData<T>, +} + +impl<T> From<i32> for Foo<T> { + fn from(id: i32) -> Self { + Self { id, _phantom_data: PhantomData } + } +} +"#####, + ) +} + +#[test] fn doctest_generate_trait_from_impl() { check_doc_test( "generate_trait_from_impl", diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 87a4c2ef758..2c8cb6e4d91 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -594,12 +594,10 @@ fn generate_impl_text_inner( let generic_params = adt.generic_param_list().map(|generic_params| { let lifetime_params = generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().map(|param| { - match param { + let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { + let param = match param { ast::TypeOrConstParam::Type(param) => { - let param = param.clone_for_update(); // remove defaults since they can't be specified in impls - param.remove_default(); let mut bounds = param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); if let Some(trait_) = trait_text { @@ -610,17 +608,16 @@ fn generate_impl_text_inner( } }; // `{ty_param}: {bounds}` - let param = - make::type_param(param.name().unwrap(), make::type_bound_list(bounds)); + let param = make::type_param(param.name()?, make::type_bound_list(bounds)); ast::GenericParam::TypeParam(param) } ast::TypeOrConstParam::Const(param) => { - let param = param.clone_for_update(); // remove defaults since they can't be specified in impls - param.remove_default(); + let param = make::const_param(param.name()?, param.ty()?); ast::GenericParam::ConstParam(param) } - } + }; + Some(param) }); make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) @@ -695,12 +692,10 @@ fn generate_impl_inner( let generic_params = adt.generic_param_list().map(|generic_params| { let lifetime_params = generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().map(|param| { - match param { + let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { + let param = match param { ast::TypeOrConstParam::Type(param) => { - let param = param.clone_for_update(); // remove defaults since they can't be specified in impls - param.remove_default(); let mut bounds = param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); if let Some(trait_) = &trait_ { @@ -711,17 +706,16 @@ fn generate_impl_inner( } }; // `{ty_param}: {bounds}` - let param = - make::type_param(param.name().unwrap(), make::type_bound_list(bounds)); + let param = make::type_param(param.name()?, make::type_bound_list(bounds)); ast::GenericParam::TypeParam(param) } ast::TypeOrConstParam::Const(param) => { - let param = param.clone_for_update(); // remove defaults since they can't be specified in impls - param.remove_default(); + let param = make::const_param(param.name()?, param.ty()?); ast::GenericParam::ConstParam(param) } - } + }; + Some(param) }); make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) @@ -749,16 +743,23 @@ fn generate_impl_inner( .clone_for_update(); // Copy any cfg attrs from the original adt - let cfg_attrs = adt - .attrs() - .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)); - for attr in cfg_attrs { - impl_.add_attr(attr.clone_for_update()); - } + add_cfg_attrs_to(adt, &impl_); impl_ } +pub(crate) fn add_cfg_attrs_to<T, U>(from: &T, to: &U) +where + T: HasAttrs, + U: AttrsOwnerEdit, +{ + let cfg_attrs = + from.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + for attr in cfg_attrs { + to.add_attr(attr.clone_for_update()); + } +} + pub(crate) fn add_method_to_adt( builder: &mut SourceChangeBuilder, adt: &ast::Adt, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index 092219a058a..975c2f02259 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -37,6 +37,7 @@ use ide_db::{ SymbolKind, documentation::HasDocs, path_transform::PathTransform, syntax_helpers::prettify_macro_expansion, traits::get_missing_assoc_items, }; +use syntax::ast::HasGenericParams; use syntax::{ AstNode, SmolStr, SyntaxElement, SyntaxKind, T, TextRange, ToSmolStr, ast::{self, HasGenericArgs, HasTypeBounds, edit_in_place::AttrsOwnerEdit, make}, @@ -390,6 +391,12 @@ fn add_type_alias_impl( } else if let Some(end) = transformed_ty.eq_token().map(|tok| tok.text_range().start()) { end + } else if let Some(end) = transformed_ty + .where_clause() + .and_then(|wc| wc.where_token()) + .map(|tok| tok.text_range().start()) + { + end } else if let Some(end) = transformed_ty.semicolon_token().map(|tok| tok.text_range().start()) { @@ -400,17 +407,29 @@ fn add_type_alias_impl( let len = end - start; let mut decl = transformed_ty.syntax().text().slice(..len).to_string(); - if !decl.ends_with(' ') { - decl.push(' '); - } - decl.push_str("= "); + decl.truncate(decl.trim_end().len()); + decl.push_str(" = "); + + let wc = transformed_ty + .where_clause() + .map(|wc| { + let ws = wc + .where_token() + .and_then(|it| it.prev_token()) + .filter(|token| token.kind() == SyntaxKind::WHITESPACE) + .map(|token| token.to_string()) + .unwrap_or_else(|| " ".into()); + format!("{ws}{wc}") + }) + .unwrap_or_default(); match ctx.config.snippet_cap { Some(cap) => { - let snippet = format!("{decl}$0;"); + let snippet = format!("{decl}$0{wc};"); item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet)); } None => { + decl.push_str(&wc); item.text_edit(TextEdit::replace(replacement_range, decl)); } }; @@ -1440,6 +1459,30 @@ impl<'b> Tr<'b> for () { "#, ); } + #[test] + fn includes_where_clause() { + check_edit( + "type Ty", + r#" +trait Tr { + type Ty where Self: Copy; +} + +impl Tr for () { + $0 +} +"#, + r#" +trait Tr { + type Ty where Self: Copy; +} + +impl Tr for () { + type Ty = $0 where Self: Copy; +} +"#, + ); + } #[test] fn strips_comments() { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs index 179d6693602..ac32649d4ff 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs @@ -458,6 +458,33 @@ type O = $0; r" struct A; trait B { +type O<'a> +where +Self: 'a; +} +impl B for A { +$0 +} +", + r#" +struct A; +trait B { +type O<'a> +where +Self: 'a; +} +impl B for A { +type O<'a> = $0 +where +Self: 'a; +} +"#, + ); + check_edit( + "type O", + r" +struct A; +trait B { type O: ?Sized = u32; } impl B for A { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index de046e70c67..973256c470f 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -11,6 +11,40 @@ }, [ FileSymbol { + name: "A", + def: Variant( + Variant { + id: EnumVariantId( + 7800, + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + EditionedFileId( + Id(2000), + ), + ), + ptr: SyntaxNodePtr { + kind: VARIANT, + range: 201..202, + }, + name_ptr: AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 201..202, + }, + ), + }, + container_name: Some( + "Enum", + ), + is_alias: false, + is_assoc: true, + is_import: false, + do_not_complete: Yes, + }, + FileSymbol { name: "Alias", def: TypeAlias( TypeAlias { @@ -43,6 +77,40 @@ do_not_complete: Yes, }, FileSymbol { + name: "B", + def: Variant( + Variant { + id: EnumVariantId( + 7801, + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + EditionedFileId( + Id(2000), + ), + ), + ptr: SyntaxNodePtr { + kind: VARIANT, + range: 204..205, + }, + name_ptr: AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 204..205, + }, + ), + }, + container_name: Some( + "Enum", + ), + is_alias: false, + is_assoc: true, + is_import: false, + do_not_complete: Yes, + }, + FileSymbol { name: "CONST", def: Const( Const { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs index 06f35759420..7402133f747 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs @@ -183,4 +183,28 @@ fn main() { "#, ); } + + #[test] + fn generic_assoc_type_infer_lifetime_in_expr_position() { + check_diagnostics( + r#" +//- minicore: sized +struct Player; + +struct Foo<'c, C> { + _v: &'c C, +} +trait WithSignals: Sized { + type SignalCollection<'c, C>; + fn __signals_from_external(&self) -> Self::SignalCollection<'_, Self>; +} +impl WithSignals for Player { + type SignalCollection<'c, C> = Foo<'c, C>; + fn __signals_from_external(&self) -> Self::SignalCollection<'_, Self> { + Self::SignalCollection { _v: self } + } +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index d8f6e813d80..17caf630182 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -983,4 +983,19 @@ fn test() { "#, ); } + + #[test] + fn naked_asm_is_safe() { + check_diagnostics( + r#" +#[rustc_builtin_macro] +macro_rules! naked_asm { () => {} } + +#[unsafe(naked)] +extern "C" fn naked() { + naked_asm!(""); +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index f58202a4213..a5d9a10d2e5 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -505,7 +505,7 @@ fn map_links<'e>( Event::End(Tag::Link(link_type, target, _)) => { in_link = false; Event::End(Tag::Link( - end_link_type.unwrap_or(link_type), + end_link_type.take().unwrap_or(link_type), end_link_target.take().unwrap_or(target), CowStr::Borrowed(""), )) @@ -514,7 +514,7 @@ fn map_links<'e>( let (link_type, link_target_s, link_name) = callback(&end_link_target.take().unwrap(), &s, range, end_link_type.unwrap()); end_link_target = Some(CowStr::Boxed(link_target_s.into())); - if !matches!(end_link_type, Some(LinkType::Autolink)) { + if !matches!(end_link_type, Some(LinkType::Autolink)) && link_type.is_some() { end_link_type = link_type; } Event::Text(CowStr::Boxed(link_name.into())) @@ -523,7 +523,7 @@ fn map_links<'e>( let (link_type, link_target_s, link_name) = callback(&end_link_target.take().unwrap(), &s, range, end_link_type.unwrap()); end_link_target = Some(CowStr::Boxed(link_target_s.into())); - if !matches!(end_link_type, Some(LinkType::Autolink)) { + if !matches!(end_link_type, Some(LinkType::Autolink)) && link_type.is_some() { end_link_type = link_type; } Event::Code(CowStr::Boxed(link_name.into())) diff --git a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs index 9bd8504733a..c081796d078 100755 --- a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs +++ b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs @@ -23,6 +23,7 @@ pub enum FoldKind { WhereClause, ReturnType, MatchArm, + Function, // region: item runs Modules, Consts, @@ -47,6 +48,7 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { let mut res = vec![]; let mut visited_comments = FxHashSet::default(); let mut visited_nodes = FxHashSet::default(); + let mut merged_fn_bodies = FxHashSet::default(); // regions can be nested, here is a LIFO buffer let mut region_starts: Vec<TextSize> = vec![]; @@ -59,6 +61,32 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { NodeOrToken::Token(token) => token.text().contains('\n'), }; if is_multiline { + // for the func with multiline param list + if matches!(element.kind(), FN) { + if let NodeOrToken::Node(node) = &element { + if let Some(fn_node) = ast::Fn::cast(node.clone()) { + if !fn_node + .param_list() + .map(|param_list| param_list.syntax().text().contains_char('\n')) + .unwrap_or(false) + { + continue; + } + + if let Some(body) = fn_node.body() { + res.push(Fold { + range: TextRange::new( + node.text_range().start(), + node.text_range().end(), + ), + kind: FoldKind::Function, + }); + merged_fn_bodies.insert(body.syntax().text_range()); + continue; + } + } + } + } res.push(Fold { range: element.text_range(), kind }); continue; } @@ -152,6 +180,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { ARG_LIST | PARAM_LIST | GENERIC_ARG_LIST | GENERIC_PARAM_LIST => Some(FoldKind::ArgList), ARRAY_EXPR => Some(FoldKind::Array), RET_TYPE => Some(FoldKind::ReturnType), + FN => Some(FoldKind::Function), WHERE_CLAUSE => Some(FoldKind::WhereClause), ASSOC_ITEM_LIST | RECORD_FIELD_LIST @@ -291,6 +320,7 @@ mod tests { use super::*; + #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let (ranges, text) = extract_tags(ra_fixture, "fold"); @@ -322,6 +352,7 @@ mod tests { FoldKind::WhereClause => "whereclause", FoldKind::ReturnType => "returntype", FoldKind::MatchArm => "matcharm", + FoldKind::Function => "function", FoldKind::TraitAliases => "traitaliases", FoldKind::ExternCrates => "externcrates", }; @@ -330,6 +361,23 @@ mod tests { } #[test] + fn test_fold_func_with_multiline_param_list() { + check( + r#" +<fold function>fn func<fold arglist>( + a: i32, + b: i32, + c: i32, +)</fold> <fold block>{ + + + +}</fold></fold> +"#, + ); + } + + #[test] fn test_fold_comments() { check( r#" @@ -541,10 +589,10 @@ const _: S = S <fold block>{ fn fold_multiline_params() { check( r#" -fn foo<fold arglist>( +<fold function>fn foo<fold arglist>( x: i32, y: String, -)</fold> {} +)</fold> {}</fold> "#, ) } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index f63499aa0fd..c5480217a91 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -10958,3 +10958,68 @@ fn bar$0() -> Foo { "#]], ); } + +#[test] +fn regression_20190() { + check( + r#" +struct Foo; + +/// [`foo` bar](Foo). +fn has_docs$0() {} + "#, + expect. + "#]], + ); +} + +#[test] +fn regression_20225() { + check( + r#" +//- minicore: coerce_unsized +trait Trait { + type Type<'a, T: ?Sized + 'a>; +} + +enum Borrowed {} + +impl Trait for Borrowed { + type Type<'a, T: ?Sized + 'a> = &'a T; +} + +enum Enum<'a, T: Trait + 'a> { + Variant1(T::Type<'a, [Enum<'a, T>]>), + Variant2, +} + +impl Enum<'_, Borrowed> { + const CONSTANT$0: Self = Self::Variant1(&[Self::Variant2]); +} + "#, + expect![[r#" + *CONSTANT* + + ```rust + ra_test_fixture::Enum + ``` + + ```rust + const CONSTANT: Self = Variant1(&[Variant2]) + ``` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs index bf4688e9d82..d0539abe282 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs @@ -92,7 +92,7 @@ pub(super) fn hints( }, MirSpan::Unknown => continue, }; - let binding = &hir.bindings[binding_idx]; + let binding = &hir[binding_idx]; let name = binding.name.display_no_db(display_target.edition).to_smolstr(); if name.starts_with("<ra@") { continue; // Ignore desugared variables diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs index cd01c075832..0da1785234a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs @@ -17,8 +17,12 @@ pub(super) fn hints( let parent = path.syntax().parent()?; let range = match path { Either::Left(path) => { - let paren = - parent.ancestors().take_while(|it| ast::ParenType::can_cast(it.kind())).last(); + let paren = parent + .ancestors() + .take_while(|it| { + ast::ParenType::can_cast(it.kind()) || ast::ForType::can_cast(it.kind()) + }) + .last(); let parent = paren.as_ref().and_then(|it| it.parent()).unwrap_or(parent); if ast::TypeBound::can_cast(parent.kind()) || ast::TypeAnchor::can_cast(parent.kind()) @@ -34,7 +38,7 @@ pub(super) fn hints( return None; } sema.resolve_trait(&path.path()?)?; - paren.map_or_else(|| path.syntax().text_range(), |it| it.text_range()) + path.syntax().text_range() } Either::Right(dyn_) => { if dyn_.dyn_token().is_some() { @@ -89,7 +93,7 @@ fn foo(_: &T, _: for<'a> T) {} impl T {} // ^ dyn impl T for (T) {} - // ^^^ dyn + // ^ dyn impl T "#, ); @@ -112,7 +116,7 @@ fn foo( _: &mut (T + T) // ^^^^^ dyn _: *mut (T), - // ^^^ dyn + // ^ dyn ) {} "#, ); @@ -136,4 +140,26 @@ fn foo( "#]], ); } + + #[test] + fn hrtb_bound_does_not_add_dyn() { + check( + r#" +//- minicore: fn +fn test<F>(f: F) where F: for<'a> FnOnce(&'a i32) {} + // ^: Sized + "#, + ); + } + + #[test] + fn with_parentheses() { + check( + r#" +trait T {} +fn foo(v: &(T)) {} + // ^ dyn + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs index 0ac25da3294..2b4151e3b75 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs @@ -4,7 +4,7 @@ use crate::grammar::attributes::ATTRIBUTE_FIRST; use super::*; -pub(super) use atom::{EXPR_RECOVERY_SET, LITERAL_FIRST, literal}; +pub(super) use atom::{EXPR_RECOVERY_SET, LITERAL_FIRST, literal, parse_asm_expr}; pub(crate) use atom::{block_expr, match_arm_list}; #[derive(PartialEq, Eq)] diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index 8ed0fc6729f..76656567e7f 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -253,8 +253,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { let m = p.start(); p.bump_remap(T![builtin]); p.bump(T![#]); - if p.at_contextual_kw(T![offset_of]) { - p.bump_remap(T![offset_of]); + if p.eat_contextual_kw(T![offset_of]) { p.expect(T!['(']); type_(p); p.expect(T![,]); @@ -278,8 +277,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { p.expect(T![')']); } Some(m.complete(p, OFFSET_OF_EXPR)) - } else if p.at_contextual_kw(T![format_args]) { - p.bump_remap(T![format_args]); + } else if p.eat_contextual_kw(T![format_args]) { p.expect(T!['(']); expr(p); if p.eat(T![,]) { @@ -302,7 +300,16 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { } p.expect(T![')']); Some(m.complete(p, FORMAT_ARGS_EXPR)) - } else if p.at_contextual_kw(T![asm]) { + } else if p.eat_contextual_kw(T![asm]) + || p.eat_contextual_kw(T![global_asm]) + || p.eat_contextual_kw(T![naked_asm]) + { + // test asm_kinds + // fn foo() { + // builtin#asm(""); + // builtin#global_asm(""); + // builtin#naked_asm(""); + // } parse_asm_expr(p, m) } else { m.abandon(p); @@ -321,8 +328,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { // tmp = out(reg) _, // ); // } -fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> { - p.bump_remap(T![asm]); +pub(crate) fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> { p.expect(T!['(']); if expr(p).is_none() { p.err_and_bump("expected asm template"); @@ -411,11 +417,10 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> { dir_spec.abandon(p); op.abandon(p); op_n.abandon(p); - p.err_and_bump("expected asm operand"); - // improves error recovery and handles err_and_bump recovering from `{` which gets - // the parser stuck here + // improves error recovery if p.at(T!['{']) { + p.error("expected asm operand"); // test_err bad_asm_expr // fn foo() { // builtin#asm( @@ -423,6 +428,8 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> { // ); // } expr(p); + } else { + p.err_and_bump("expected asm operand"); } if p.at(T!['}']) { diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs index b9f4866574a..8e551b0b961 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs @@ -261,6 +261,19 @@ fn opt_item_without_modifiers(p: &mut Parser<'_>, m: Marker) -> Result<(), Marke T![const] if (la == IDENT || la == T![_] || la == T![mut]) => consts::konst(p, m), T![static] if (la == IDENT || la == T![_] || la == T![mut]) => consts::static_(p, m), + IDENT + if p.at_contextual_kw(T![builtin]) + && p.nth_at(1, T![#]) + && p.nth_at_contextual_kw(2, T![global_asm]) => + { + p.bump_remap(T![builtin]); + p.bump(T![#]); + p.bump_remap(T![global_asm]); + // test global_asm + // builtin#global_asm("") + expressions::parse_asm_expr(p, m); + } + _ => return Err(m), }; Ok(()) diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index bff9acd78fa..8fff1c3db74 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -11,8 +11,8 @@ use std::ops; use rustc_literal_escaper::{ - unescape_byte, unescape_byte_str, unescape_c_str, unescape_char, unescape_str, EscapeError, - Mode, + EscapeError, Mode, unescape_byte, unescape_byte_str, unescape_c_str, unescape_char, + unescape_str, }; use crate::{ diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs index 36a363afe93..ca02d9fdfde 100644 --- a/src/tools/rust-analyzer/crates/parser/src/parser.rs +++ b/src/tools/rust-analyzer/crates/parser/src/parser.rs @@ -29,7 +29,7 @@ pub(crate) struct Parser<'t> { edition: Edition, } -const PARSER_STEP_LIMIT: usize = 15_000_000; +const PARSER_STEP_LIMIT: usize = if cfg!(debug_assertions) { 150_000 } else { 15_000_000 }; impl<'t> Parser<'t> { pub(super) fn new(inp: &'t Input, edition: Edition) -> Parser<'t> { @@ -254,7 +254,10 @@ impl<'t> Parser<'t> { /// Create an error node and consume the next token. pub(crate) fn err_and_bump(&mut self, message: &str) { - self.err_recover(message, TokenSet::EMPTY); + let m = self.start(); + self.error(message); + self.bump_any(); + m.complete(self, ERROR); } /// Create an error node and consume the next token unless it is in the recovery set. diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs index f534546ea07..12a13caa4d9 100644 --- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs +++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs @@ -120,12 +120,14 @@ pub enum SyntaxKind { DYN_KW, FORMAT_ARGS_KW, GEN_KW, + GLOBAL_ASM_KW, INLATEOUT_KW, INOUT_KW, LABEL_KW, LATEOUT_KW, MACRO_RULES_KW, MAY_UNWIND_KW, + NAKED_ASM_KW, NOMEM_KW, NORETURN_KW, NOSTACK_KW, @@ -599,12 +601,14 @@ impl SyntaxKind { DEFAULT_KW => "default", DYN_KW => "dyn", FORMAT_ARGS_KW => "format_args", + GLOBAL_ASM_KW => "global_asm", INLATEOUT_KW => "inlateout", INOUT_KW => "inout", LABEL_KW => "label", LATEOUT_KW => "lateout", MACRO_RULES_KW => "macro_rules", MAY_UNWIND_KW => "may_unwind", + NAKED_ASM_KW => "naked_asm", NOMEM_KW => "nomem", NORETURN_KW => "noreturn", NOSTACK_KW => "nostack", @@ -699,12 +703,14 @@ impl SyntaxKind { DEFAULT_KW => true, DYN_KW if edition < Edition::Edition2018 => true, FORMAT_ARGS_KW => true, + GLOBAL_ASM_KW => true, INLATEOUT_KW => true, INOUT_KW => true, LABEL_KW => true, LATEOUT_KW => true, MACRO_RULES_KW => true, MAY_UNWIND_KW => true, + NAKED_ASM_KW => true, NOMEM_KW => true, NORETURN_KW => true, NOSTACK_KW => true, @@ -787,12 +793,14 @@ impl SyntaxKind { DEFAULT_KW => true, DYN_KW if edition < Edition::Edition2018 => true, FORMAT_ARGS_KW => true, + GLOBAL_ASM_KW => true, INLATEOUT_KW => true, INOUT_KW => true, LABEL_KW => true, LATEOUT_KW => true, MACRO_RULES_KW => true, MAY_UNWIND_KW => true, + NAKED_ASM_KW => true, NOMEM_KW => true, NORETURN_KW => true, NOSTACK_KW => true, @@ -938,12 +946,14 @@ impl SyntaxKind { "default" => DEFAULT_KW, "dyn" if edition < Edition::Edition2018 => DYN_KW, "format_args" => FORMAT_ARGS_KW, + "global_asm" => GLOBAL_ASM_KW, "inlateout" => INLATEOUT_KW, "inout" => INOUT_KW, "label" => LABEL_KW, "lateout" => LATEOUT_KW, "macro_rules" => MACRO_RULES_KW, "may_unwind" => MAY_UNWIND_KW, + "naked_asm" => NAKED_ASM_KW, "nomem" => NOMEM_KW, "noreturn" => NORETURN_KW, "nostack" => NOSTACK_KW, @@ -998,7 +1008,7 @@ impl SyntaxKind { } } #[macro_export] -macro_rules ! T_ { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [att_syntax] => { $ crate :: SyntaxKind :: ATT_SYNTAX_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [clobber_abi] => { $ crate :: SyntaxKind :: CLOBBER_ABI_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [inlateout] => { $ crate :: SyntaxKind :: INLATEOUT_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [label] => { $ crate :: SyntaxKind :: LABEL_KW } ; [lateout] => { $ crate :: SyntaxKind :: LATEOUT_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [may_unwind] => { $ crate :: SyntaxKind :: MAY_UNWIND_KW } ; [nomem] => { $ crate :: SyntaxKind :: NOMEM_KW } ; [noreturn] => { $ crate :: SyntaxKind :: NORETURN_KW } ; [nostack] => { $ crate :: SyntaxKind :: NOSTACK_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [options] => { $ crate :: SyntaxKind :: OPTIONS_KW } ; [out] => { $ crate :: SyntaxKind :: OUT_KW } ; [preserves_flags] => { $ crate :: SyntaxKind :: PRESERVES_FLAGS_KW } ; [pure] => { $ crate :: SyntaxKind :: PURE_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [readonly] => { $ crate :: SyntaxKind :: READONLY_KW } ; [safe] => { $ crate :: SyntaxKind :: SAFE_KW } ; [sym] => { $ crate :: SyntaxKind :: SYM_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; [frontmatter] => { $ crate :: SyntaxKind :: FRONTMATTER } ; } +macro_rules ! T_ { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [att_syntax] => { $ crate :: SyntaxKind :: ATT_SYNTAX_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [clobber_abi] => { $ crate :: SyntaxKind :: CLOBBER_ABI_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [global_asm] => { $ crate :: SyntaxKind :: GLOBAL_ASM_KW } ; [inlateout] => { $ crate :: SyntaxKind :: INLATEOUT_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [label] => { $ crate :: SyntaxKind :: LABEL_KW } ; [lateout] => { $ crate :: SyntaxKind :: LATEOUT_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [may_unwind] => { $ crate :: SyntaxKind :: MAY_UNWIND_KW } ; [naked_asm] => { $ crate :: SyntaxKind :: NAKED_ASM_KW } ; [nomem] => { $ crate :: SyntaxKind :: NOMEM_KW } ; [noreturn] => { $ crate :: SyntaxKind :: NORETURN_KW } ; [nostack] => { $ crate :: SyntaxKind :: NOSTACK_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [options] => { $ crate :: SyntaxKind :: OPTIONS_KW } ; [out] => { $ crate :: SyntaxKind :: OUT_KW } ; [preserves_flags] => { $ crate :: SyntaxKind :: PRESERVES_FLAGS_KW } ; [pure] => { $ crate :: SyntaxKind :: PURE_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [readonly] => { $ crate :: SyntaxKind :: READONLY_KW } ; [safe] => { $ crate :: SyntaxKind :: SAFE_KW } ; [sym] => { $ crate :: SyntaxKind :: SYM_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; [frontmatter] => { $ crate :: SyntaxKind :: FRONTMATTER } ; } impl ::core::marker::Copy for SyntaxKind {} impl ::core::clone::Clone for SyntaxKind { #[inline] diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs index 6ec4192830b..cef7b0ee239 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs @@ -21,6 +21,8 @@ mod ok { #[test] fn asm_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_expr.rs"); } #[test] + fn asm_kinds() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_kinds.rs"); } + #[test] fn asm_label() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_label.rs"); } #[test] fn assoc_const_eq() { @@ -298,6 +300,8 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/generic_param_list.rs"); } #[test] + fn global_asm() { run_and_expect_no_errors("test_data/parser/inline/ok/global_asm.rs"); } + #[test] fn half_open_range_pat() { run_and_expect_no_errors("test_data/parser/inline/ok/half_open_range_pat.rs"); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_kinds.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_kinds.rast new file mode 100644 index 00000000000..c337d89aa50 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_kinds.rast @@ -0,0 +1,48 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + ASM_KW "asm" + L_PAREN "(" + LITERAL + STRING "\"\"" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + GLOBAL_ASM_KW "global_asm" + L_PAREN "(" + LITERAL + STRING "\"\"" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + NAKED_ASM_KW "naked_asm" + L_PAREN "(" + LITERAL + STRING "\"\"" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_kinds.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_kinds.rs new file mode 100644 index 00000000000..9c03e9de689 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_kinds.rs @@ -0,0 +1,5 @@ +fn foo() { + builtin#asm(""); + builtin#global_asm(""); + builtin#naked_asm(""); +} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/global_asm.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/global_asm.rast new file mode 100644 index 00000000000..5337c56be17 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/global_asm.rast @@ -0,0 +1,10 @@ +SOURCE_FILE + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + GLOBAL_ASM_KW "global_asm" + L_PAREN "(" + LITERAL + STRING "\"\"" + R_PAREN ")" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/global_asm.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/global_asm.rs new file mode 100644 index 00000000000..967ce1f5fd9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/global_asm.rs @@ -0,0 +1 @@ +builtin#global_asm("") diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs new file mode 100644 index 00000000000..7966f74df30 --- /dev/null +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs @@ -0,0 +1,34 @@ +//! Read `.cargo/config.toml` as a JSON object +use rustc_hash::FxHashMap; +use toolchain::Tool; + +use crate::{ManifestPath, Sysroot, utf8_stdout}; + +pub(crate) type CargoConfigFile = serde_json::Map<String, serde_json::Value>; + +pub(crate) fn read( + manifest: &ManifestPath, + extra_env: &FxHashMap<String, Option<String>>, + sysroot: &Sysroot, +) -> Option<CargoConfigFile> { + let mut cargo_config = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env); + cargo_config + .args(["-Z", "unstable-options", "config", "get", "--format", "json"]) + .env("RUSTC_BOOTSTRAP", "1"); + if manifest.is_rust_manifest() { + cargo_config.arg("-Zscript"); + } + + tracing::debug!("Discovering cargo config by {:?}", cargo_config); + let json: serde_json::Map<String, serde_json::Value> = utf8_stdout(&mut cargo_config) + .inspect(|json| { + tracing::debug!("Discovered cargo config: {:?}", json); + }) + .inspect_err(|err| { + tracing::debug!("Failed to discover cargo config: {:?}", err); + }) + .ok() + .and_then(|stdout| serde_json::from_str(&stdout).ok())?; + + Some(json) +} diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index 4bacc904174..daadcd9d79a 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -300,8 +300,6 @@ pub struct CargoMetadataConfig { pub extra_args: Vec<String>, /// Extra env vars to set when invoking the cargo command pub extra_env: FxHashMap<String, Option<String>>, - /// The target dir for this workspace load. - pub target_dir: Utf8PathBuf, /// What kind of metadata are we fetching: workspace, rustc, or sysroot. pub kind: &'static str, /// The toolchain version, if known. @@ -317,188 +315,6 @@ struct PackageMetadata { } impl CargoWorkspace { - /// Fetches the metadata for the given `cargo_toml` manifest. - /// A successful result may contain another metadata error if the initial fetching failed but - /// the `--no-deps` retry succeeded. - /// - /// The sysroot is used to set the `RUSTUP_TOOLCHAIN` env var when invoking cargo - /// to ensure that the rustup proxy uses the correct toolchain. - pub fn fetch_metadata( - cargo_toml: &ManifestPath, - current_dir: &AbsPath, - config: &CargoMetadataConfig, - sysroot: &Sysroot, - no_deps: bool, - locked: bool, - progress: &dyn Fn(String), - ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> { - let res = Self::fetch_metadata_( - cargo_toml, - current_dir, - config, - sysroot, - no_deps, - locked, - progress, - ); - if let Ok((_, Some(ref e))) = res { - tracing::warn!( - %cargo_toml, - ?e, - "`cargo metadata` failed, but retry with `--no-deps` succeeded" - ); - } - res - } - - fn fetch_metadata_( - cargo_toml: &ManifestPath, - current_dir: &AbsPath, - config: &CargoMetadataConfig, - sysroot: &Sysroot, - no_deps: bool, - locked: bool, - progress: &dyn Fn(String), - ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> { - let cargo = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env); - let mut meta = MetadataCommand::new(); - meta.cargo_path(cargo.get_program()); - cargo.get_envs().for_each(|(var, val)| _ = meta.env(var, val.unwrap_or_default())); - meta.manifest_path(cargo_toml.to_path_buf()); - match &config.features { - CargoFeatures::All => { - meta.features(CargoOpt::AllFeatures); - } - CargoFeatures::Selected { features, no_default_features } => { - if *no_default_features { - meta.features(CargoOpt::NoDefaultFeatures); - } - if !features.is_empty() { - meta.features(CargoOpt::SomeFeatures(features.clone())); - } - } - } - meta.current_dir(current_dir); - - let mut other_options = vec![]; - // cargo metadata only supports a subset of flags of what cargo usually accepts, and usually - // the only relevant flags for metadata here are unstable ones, so we pass those along - // but nothing else - let mut extra_args = config.extra_args.iter(); - while let Some(arg) = extra_args.next() { - if arg == "-Z" { - if let Some(arg) = extra_args.next() { - other_options.push("-Z".to_owned()); - other_options.push(arg.to_owned()); - } - } - } - - if !config.targets.is_empty() { - other_options.extend( - config.targets.iter().flat_map(|it| ["--filter-platform".to_owned(), it.clone()]), - ); - } - if no_deps { - other_options.push("--no-deps".to_owned()); - } - - let mut using_lockfile_copy = false; - // The manifest is a rust file, so this means its a script manifest - if cargo_toml.is_rust_manifest() { - other_options.push("-Zscript".to_owned()); - } else if config - .toolchain_version - .as_ref() - .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH) - { - let lockfile = <_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock"); - let target_lockfile = config - .target_dir - .join("rust-analyzer") - .join("metadata") - .join(config.kind) - .join("Cargo.lock"); - match std::fs::copy(&lockfile, &target_lockfile) { - Ok(_) => { - using_lockfile_copy = true; - other_options.push("--lockfile-path".to_owned()); - other_options.push(target_lockfile.to_string()); - } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - // There exists no lockfile yet - using_lockfile_copy = true; - other_options.push("--lockfile-path".to_owned()); - other_options.push(target_lockfile.to_string()); - } - Err(e) => { - tracing::warn!( - "Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}", - ); - } - } - } - if using_lockfile_copy { - other_options.push("-Zunstable-options".to_owned()); - meta.env("RUSTC_BOOTSTRAP", "1"); - } - // No need to lock it if we copied the lockfile, we won't modify the original after all/ - // This way cargo cannot error out on us if the lockfile requires updating. - if !using_lockfile_copy && locked { - other_options.push("--locked".to_owned()); - } - meta.other_options(other_options); - - // FIXME: Fetching metadata is a slow process, as it might require - // calling crates.io. We should be reporting progress here, but it's - // unclear whether cargo itself supports it. - progress("cargo metadata: started".to_owned()); - - let res = (|| -> anyhow::Result<(_, _)> { - let mut errored = false; - let output = - spawn_with_streaming_output(meta.cargo_command(), &mut |_| (), &mut |line| { - errored = errored || line.starts_with("error") || line.starts_with("warning"); - if errored { - progress("cargo metadata: ?".to_owned()); - return; - } - progress(format!("cargo metadata: {line}")); - })?; - if !output.status.success() { - progress(format!("cargo metadata: failed {}", output.status)); - let error = cargo_metadata::Error::CargoMetadata { - stderr: String::from_utf8(output.stderr)?, - } - .into(); - if !no_deps { - // If we failed to fetch metadata with deps, try again without them. - // This makes r-a still work partially when offline. - if let Ok((metadata, _)) = Self::fetch_metadata_( - cargo_toml, - current_dir, - config, - sysroot, - true, - locked, - progress, - ) { - return Ok((metadata, Some(error))); - } - } - return Err(error); - } - let stdout = from_utf8(&output.stdout)? - .lines() - .find(|line| line.starts_with('{')) - .ok_or(cargo_metadata::Error::NoJson)?; - Ok((cargo_metadata::MetadataCommand::parse(stdout)?, None)) - })() - .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command())); - progress("cargo metadata: finished".to_owned()); - res - } - pub fn new( mut meta: cargo_metadata::Metadata, ws_manifest_path: ManifestPath, @@ -733,3 +549,214 @@ impl CargoWorkspace { self.requires_rustc_private } } + +pub(crate) struct FetchMetadata { + command: cargo_metadata::MetadataCommand, + lockfile_path: Option<Utf8PathBuf>, + kind: &'static str, + no_deps: bool, + no_deps_result: anyhow::Result<cargo_metadata::Metadata>, + other_options: Vec<String>, +} + +impl FetchMetadata { + /// Builds a command to fetch metadata for the given `cargo_toml` manifest. + /// + /// Performs a lightweight pre-fetch using the `--no-deps` option, + /// available via [`FetchMetadata::no_deps_metadata`], to gather basic + /// information such as the `target-dir`. + /// + /// The provided sysroot is used to set the `RUSTUP_TOOLCHAIN` + /// environment variable when invoking Cargo, ensuring that the + /// rustup proxy selects the correct toolchain. + pub(crate) fn new( + cargo_toml: &ManifestPath, + current_dir: &AbsPath, + config: &CargoMetadataConfig, + sysroot: &Sysroot, + no_deps: bool, + ) -> Self { + let cargo = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env); + let mut command = MetadataCommand::new(); + command.cargo_path(cargo.get_program()); + cargo.get_envs().for_each(|(var, val)| _ = command.env(var, val.unwrap_or_default())); + command.manifest_path(cargo_toml.to_path_buf()); + match &config.features { + CargoFeatures::All => { + command.features(CargoOpt::AllFeatures); + } + CargoFeatures::Selected { features, no_default_features } => { + if *no_default_features { + command.features(CargoOpt::NoDefaultFeatures); + } + if !features.is_empty() { + command.features(CargoOpt::SomeFeatures(features.clone())); + } + } + } + command.current_dir(current_dir); + + let mut needs_nightly = false; + let mut other_options = vec![]; + // cargo metadata only supports a subset of flags of what cargo usually accepts, and usually + // the only relevant flags for metadata here are unstable ones, so we pass those along + // but nothing else + let mut extra_args = config.extra_args.iter(); + while let Some(arg) = extra_args.next() { + if arg == "-Z" { + if let Some(arg) = extra_args.next() { + needs_nightly = true; + other_options.push("-Z".to_owned()); + other_options.push(arg.to_owned()); + } + } + } + + let mut lockfile_path = None; + if cargo_toml.is_rust_manifest() { + needs_nightly = true; + other_options.push("-Zscript".to_owned()); + } else if config + .toolchain_version + .as_ref() + .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH) + { + lockfile_path = Some(<_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock")); + } + + if !config.targets.is_empty() { + other_options.extend( + config.targets.iter().flat_map(|it| ["--filter-platform".to_owned(), it.clone()]), + ); + } + + command.other_options(other_options.clone()); + + if needs_nightly { + command.env("RUSTC_BOOTSTRAP", "1"); + } + + // Pre-fetch basic metadata using `--no-deps`, which: + // - avoids fetching registries like crates.io, + // - skips dependency resolution and does not modify lockfiles, + // - and thus doesn't require progress reporting or copying lockfiles. + // + // Useful as a fast fallback to extract info like `target-dir`. + let cargo_command; + let no_deps_result = if no_deps { + command.no_deps(); + cargo_command = command.cargo_command(); + command.exec() + } else { + let mut no_deps_command = command.clone(); + no_deps_command.no_deps(); + cargo_command = no_deps_command.cargo_command(); + no_deps_command.exec() + } + .with_context(|| format!("Failed to run `{cargo_command:?}`")); + + Self { command, lockfile_path, kind: config.kind, no_deps, no_deps_result, other_options } + } + + pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> { + self.no_deps_result.as_ref().ok() + } + + /// Executes the metadata-fetching command. + /// + /// A successful result may still contain a metadata error if the full fetch failed, + /// but the fallback `--no-deps` pre-fetch succeeded during command construction. + pub(crate) fn exec( + self, + target_dir: &Utf8Path, + locked: bool, + progress: &dyn Fn(String), + ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> { + let Self { mut command, lockfile_path, kind, no_deps, no_deps_result, mut other_options } = + self; + + if no_deps { + return no_deps_result.map(|m| (m, None)); + } + + let mut using_lockfile_copy = false; + // The manifest is a rust file, so this means its a script manifest + if let Some(lockfile) = lockfile_path { + let target_lockfile = + target_dir.join("rust-analyzer").join("metadata").join(kind).join("Cargo.lock"); + match std::fs::copy(&lockfile, &target_lockfile) { + Ok(_) => { + using_lockfile_copy = true; + other_options.push("--lockfile-path".to_owned()); + other_options.push(target_lockfile.to_string()); + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + // There exists no lockfile yet + using_lockfile_copy = true; + other_options.push("--lockfile-path".to_owned()); + other_options.push(target_lockfile.to_string()); + } + Err(e) => { + tracing::warn!( + "Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}", + ); + } + } + } + if using_lockfile_copy { + other_options.push("-Zunstable-options".to_owned()); + command.env("RUSTC_BOOTSTRAP", "1"); + } + // No need to lock it if we copied the lockfile, we won't modify the original after all/ + // This way cargo cannot error out on us if the lockfile requires updating. + if !using_lockfile_copy && locked { + other_options.push("--locked".to_owned()); + } + command.other_options(other_options); + + // FIXME: Fetching metadata is a slow process, as it might require + // calling crates.io. We should be reporting progress here, but it's + // unclear whether cargo itself supports it. + progress("cargo metadata: started".to_owned()); + + let res = (|| -> anyhow::Result<(_, _)> { + let mut errored = false; + let output = + spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| { + errored = errored || line.starts_with("error") || line.starts_with("warning"); + if errored { + progress("cargo metadata: ?".to_owned()); + return; + } + progress(format!("cargo metadata: {line}")); + })?; + if !output.status.success() { + progress(format!("cargo metadata: failed {}", output.status)); + let error = cargo_metadata::Error::CargoMetadata { + stderr: String::from_utf8(output.stderr)?, + } + .into(); + if !no_deps { + // If we failed to fetch metadata with deps, return pre-fetched result without them. + // This makes r-a still work partially when offline. + if let Ok(metadata) = no_deps_result { + tracing::warn!( + ?error, + "`cargo metadata` failed and returning succeeded result with `--no-deps`" + ); + return Ok((metadata, Some(error))); + } + } + return Err(error); + } + let stdout = from_utf8(&output.stdout)? + .lines() + .find(|line| line.starts_with('{')) + .ok_or(cargo_metadata::Error::NoJson)?; + Ok((cargo_metadata::MetadataCommand::parse(stdout)?, None)) + })() + .with_context(|| format!("Failed to run `{:?}`", command.cargo_command())); + progress("cargo metadata: finished".to_owned()); + res + } +} diff --git a/src/tools/rust-analyzer/crates/project-model/src/env.rs b/src/tools/rust-analyzer/crates/project-model/src/env.rs index 9e0415c3b39..d281492fc98 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/env.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/env.rs @@ -1,10 +1,9 @@ //! Cargo-like environment variables injection. use base_db::Env; -use paths::{Utf8Path, Utf8PathBuf}; -use rustc_hash::FxHashMap; +use paths::Utf8Path; use toolchain::Tool; -use crate::{ManifestPath, PackageData, Sysroot, TargetKind, utf8_stdout}; +use crate::{ManifestPath, PackageData, TargetKind, cargo_config_file::CargoConfigFile}; /// Recreates the compile-time environment variables that Cargo sets. /// @@ -61,104 +60,68 @@ pub(crate) fn inject_rustc_tool_env(env: &mut Env, cargo_name: &str, kind: Targe env.set("CARGO_CRATE_NAME", cargo_name.replace('-', "_")); } -pub(crate) fn cargo_config_env( - manifest: &ManifestPath, - extra_env: &FxHashMap<String, Option<String>>, - sysroot: &Sysroot, -) -> Env { - let mut cargo_config = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env); - cargo_config - .args(["-Z", "unstable-options", "config", "get", "env"]) - .env("RUSTC_BOOTSTRAP", "1"); - if manifest.is_rust_manifest() { - cargo_config.arg("-Zscript"); - } - // if successful we receive `env.key.value = "value" per entry - tracing::debug!("Discovering cargo config env by {:?}", cargo_config); - utf8_stdout(&mut cargo_config) - .map(|stdout| parse_output_cargo_config_env(manifest, &stdout)) - .inspect(|env| { - tracing::debug!("Discovered cargo config env: {:?}", env); - }) - .inspect_err(|err| { - tracing::debug!("Failed to discover cargo config env: {:?}", err); - }) - .unwrap_or_default() -} - -fn parse_output_cargo_config_env(manifest: &ManifestPath, stdout: &str) -> Env { +pub(crate) fn cargo_config_env(manifest: &ManifestPath, config: &Option<CargoConfigFile>) -> Env { let mut env = Env::default(); - let mut relatives = vec![]; - for (key, val) in - stdout.lines().filter_map(|l| l.strip_prefix("env.")).filter_map(|l| l.split_once(" = ")) - { - let val = val.trim_matches('"').to_owned(); - if let Some((key, modifier)) = key.split_once('.') { - match modifier { - "relative" => relatives.push((key, val)), - "value" => _ = env.insert(key, val), - _ => { - tracing::warn!( - "Unknown modifier in cargo config env: {}, expected `relative` or `value`", - modifier - ); - continue; - } - } - } else { - env.insert(key, val); - } - } + let Some(serde_json::Value::Object(env_json)) = config.as_ref().and_then(|c| c.get("env")) + else { + return env; + }; + // FIXME: The base here should be the parent of the `.cargo/config` file, not the manifest. // But cargo does not provide this information. let base = <_ as AsRef<Utf8Path>>::as_ref(manifest.parent()); - for (key, relative) in relatives { - if relative != "true" { + + for (key, entry) in env_json { + let serde_json::Value::Object(entry) = entry else { continue; - } - if let Some(suffix) = env.get(key) { - env.insert(key, base.join(suffix).to_string()); - } - } - env -} + }; + let Some(value) = entry.get("value").and_then(|v| v.as_str()) else { + continue; + }; -pub(crate) fn cargo_config_build_target_dir( - manifest: &ManifestPath, - extra_env: &FxHashMap<String, Option<String>>, - sysroot: &Sysroot, -) -> Option<Utf8PathBuf> { - let mut cargo_config = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env); - cargo_config - .args(["-Z", "unstable-options", "config", "get", "build.target-dir"]) - .env("RUSTC_BOOTSTRAP", "1"); - if manifest.is_rust_manifest() { - cargo_config.arg("-Zscript"); + let value = if entry + .get("relative") + .and_then(|v| v.as_bool()) + .is_some_and(std::convert::identity) + { + base.join(value).to_string() + } else { + value.to_owned() + }; + env.insert(key, value); } - utf8_stdout(&mut cargo_config) - .map(|stdout| { - Utf8Path::new(stdout.trim_start_matches("build.target-dir = ").trim_matches('"')) - .to_owned() - }) - .ok() + + env } #[test] fn parse_output_cargo_config_env_works() { - let stdout = r#" -env.CARGO_WORKSPACE_DIR.relative = true -env.CARGO_WORKSPACE_DIR.value = "" -env.RELATIVE.relative = true -env.RELATIVE.value = "../relative" -env.INVALID.relative = invalidbool -env.INVALID.value = "../relative" -env.TEST.value = "test" -"# - .trim(); + let raw = r#" +{ + "env": { + "CARGO_WORKSPACE_DIR": { + "relative": true, + "value": "" + }, + "INVALID": { + "relative": "invalidbool", + "value": "../relative" + }, + "RELATIVE": { + "relative": true, + "value": "../relative" + }, + "TEST": { + "value": "test" + } + } +} +"#; + let config: CargoConfigFile = serde_json::from_str(raw).unwrap(); let cwd = paths::Utf8PathBuf::try_from(std::env::current_dir().unwrap()).unwrap(); let manifest = paths::AbsPathBuf::assert(cwd.join("Cargo.toml")); let manifest = ManifestPath::try_from(manifest).unwrap(); - let env = parse_output_cargo_config_env(&manifest, stdout); + let env = cargo_config_env(&manifest, &Some(config)); assert_eq!(env.get("CARGO_WORKSPACE_DIR").as_deref(), Some(cwd.join("").as_str())); assert_eq!(env.get("RELATIVE").as_deref(), Some(cwd.join("../relative").as_str())); assert_eq!(env.get("INVALID").as_deref(), Some("../relative")); diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs index 436af64cf13..3bf3d06e6b1 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs @@ -24,7 +24,7 @@ pub mod toolchain_info { use std::path::Path; - use crate::{ManifestPath, Sysroot}; + use crate::{ManifestPath, Sysroot, cargo_config_file::CargoConfigFile}; #[derive(Copy, Clone)] pub enum QueryConfig<'a> { @@ -32,11 +32,12 @@ pub mod toolchain_info { Rustc(&'a Sysroot, &'a Path), /// Attempt to use cargo to query the desired information, honoring cargo configurations. /// If this fails, falls back to invoking `rustc` directly. - Cargo(&'a Sysroot, &'a ManifestPath), + Cargo(&'a Sysroot, &'a ManifestPath, &'a Option<CargoConfigFile>), } } mod build_dependencies; +mod cargo_config_file; mod cargo_workspace; mod env; mod manifest_path; diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs index 9f19260d309..9781c46737d 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs @@ -9,14 +9,15 @@ use std::{env, fs, ops::Not, path::Path, process::Command}; use anyhow::{Result, format_err}; use itertools::Itertools; -use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; use rustc_hash::FxHashMap; use stdx::format_to; use toolchain::{Tool, probe_for_binary}; use crate::{ CargoWorkspace, ManifestPath, ProjectJson, RustSourceWorkspaceConfig, - cargo_workspace::CargoMetadataConfig, utf8_stdout, + cargo_workspace::{CargoMetadataConfig, FetchMetadata}, + utf8_stdout, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -211,6 +212,7 @@ impl Sysroot { sysroot_source_config: &RustSourceWorkspaceConfig, no_deps: bool, current_dir: &AbsPath, + target_dir: &Utf8Path, progress: &dyn Fn(String), ) -> Option<RustLibSrcWorkspace> { assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded"); @@ -224,6 +226,7 @@ impl Sysroot { match self.load_library_via_cargo( &library_manifest, current_dir, + target_dir, cargo_config, no_deps, progress, @@ -319,6 +322,7 @@ impl Sysroot { &self, library_manifest: &ManifestPath, current_dir: &AbsPath, + target_dir: &Utf8Path, cargo_config: &CargoMetadataConfig, no_deps: bool, progress: &dyn Fn(String), @@ -331,16 +335,11 @@ impl Sysroot { Some("nightly".to_owned()), ); - let (mut res, _) = CargoWorkspace::fetch_metadata( - library_manifest, - current_dir, - &cargo_config, - self, - no_deps, - // Make sure we never attempt to write to the sysroot - true, - progress, - )?; + // Make sure we never attempt to write to the sysroot + let locked = true; + let (mut res, _) = + FetchMetadata::new(library_manifest, current_dir, &cargo_config, self, no_deps) + .exec(target_dir, locked, progress)?; // Patch out `rustc-std-workspace-*` crates to point to the real crates. // This is done prior to `CrateGraph` construction to prevent de-duplication logic from failing. diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index f229e9a650d..ed72520f40d 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -239,8 +239,13 @@ fn smoke_test_real_sysroot_cargo() { ); let cwd = AbsPathBuf::assert_utf8(temp_dir().join("smoke_test_real_sysroot_cargo")); std::fs::create_dir_all(&cwd).unwrap(); - let loaded_sysroot = - sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &cwd, &|_| ()); + let loaded_sysroot = sysroot.load_workspace( + &RustSourceWorkspaceConfig::default_cargo(), + false, + &cwd, + &Utf8PathBuf::default(), + &|_| (), + ); if let Some(loaded_sysroot) = loaded_sysroot { sysroot.set_workspace(loaded_sysroot); } diff --git a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs index a77f76797fc..6e06e88bf7a 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs @@ -63,7 +63,7 @@ fn rustc_print_cfg( ) -> anyhow::Result<String> { const RUSTC_ARGS: [&str; 2] = ["--print", "cfg"]; let (sysroot, current_dir) = match config { - QueryConfig::Cargo(sysroot, cargo_toml) => { + QueryConfig::Cargo(sysroot, cargo_toml, _) => { let mut cmd = sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env); cmd.args(["rustc", "-Z", "unstable-options"]).args(RUSTC_ARGS); if let Some(target) = target { @@ -109,7 +109,7 @@ mod tests { let sysroot = Sysroot::empty(); let manifest_path = ManifestPath::try_from(AbsPathBuf::assert(Utf8PathBuf::from(manifest_path))).unwrap(); - let cfg = QueryConfig::Cargo(&sysroot, &manifest_path); + let cfg = QueryConfig::Cargo(&sysroot, &manifest_path, &None); assert_ne!(get(cfg, None, &FxHashMap::default()), vec![]); } diff --git a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_data_layout.rs b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_data_layout.rs index a4d0ec69537..a28f468e692 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_data_layout.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_data_layout.rs @@ -20,7 +20,7 @@ pub fn get( }) }; let (sysroot, current_dir) = match config { - QueryConfig::Cargo(sysroot, cargo_toml) => { + QueryConfig::Cargo(sysroot, cargo_toml, _) => { let mut cmd = sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env); cmd.env("RUSTC_BOOTSTRAP", "1"); cmd.args(["rustc", "-Z", "unstable-options"]).args(RUSTC_ARGS).args([ @@ -66,7 +66,7 @@ mod tests { let sysroot = Sysroot::empty(); let manifest_path = ManifestPath::try_from(AbsPathBuf::assert(Utf8PathBuf::from(manifest_path))).unwrap(); - let cfg = QueryConfig::Cargo(&sysroot, &manifest_path); + let cfg = QueryConfig::Cargo(&sysroot, &manifest_path, &None); assert!(get(cfg, None, &FxHashMap::default()).is_ok()); } diff --git a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_tuple.rs b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_tuple.rs index f6ab8532197..9f12ededb61 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_tuple.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/target_tuple.rs @@ -5,7 +5,9 @@ use anyhow::Context; use rustc_hash::FxHashMap; use toolchain::Tool; -use crate::{ManifestPath, Sysroot, toolchain_info::QueryConfig, utf8_stdout}; +use crate::{ + Sysroot, cargo_config_file::CargoConfigFile, toolchain_info::QueryConfig, utf8_stdout, +}; /// For cargo, runs `cargo -Zunstable-options config get build.target` to get the configured project target(s). /// For rustc, runs `rustc --print -vV` to get the host target. @@ -20,8 +22,8 @@ pub fn get( } let (sysroot, current_dir) = match config { - QueryConfig::Cargo(sysroot, cargo_toml) => { - match cargo_config_build_target(cargo_toml, extra_env, sysroot) { + QueryConfig::Cargo(sysroot, cargo_toml, config_file) => { + match config_file.as_ref().and_then(cargo_config_build_target) { Some(it) => return Ok(it), None => (sysroot, cargo_toml.parent().as_ref()), } @@ -50,30 +52,30 @@ fn rustc_discover_host_tuple( } } -fn cargo_config_build_target( - cargo_toml: &ManifestPath, - extra_env: &FxHashMap<String, Option<String>>, - sysroot: &Sysroot, -) -> Option<Vec<String>> { - let mut cmd = sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env); - cmd.current_dir(cargo_toml.parent()).env("RUSTC_BOOTSTRAP", "1"); - cmd.args(["-Z", "unstable-options", "config", "get", "build.target"]); - // if successful we receive `build.target = "target-tuple"` - // or `build.target = ["<target 1>", ..]` - // this might be `error: config value `build.target` is not set` in which case we - // don't wanna log the error - utf8_stdout(&mut cmd).and_then(parse_output_cargo_config_build_target).ok() +fn cargo_config_build_target(config: &CargoConfigFile) -> Option<Vec<String>> { + match parse_json_cargo_config_build_target(config) { + Ok(v) => v, + Err(e) => { + tracing::debug!("Failed to discover cargo config build target {e:?}"); + None + } + } } // Parses `"build.target = [target-tuple, target-tuple, ...]"` or `"build.target = "target-tuple"` -fn parse_output_cargo_config_build_target(stdout: String) -> anyhow::Result<Vec<String>> { - let trimmed = stdout.trim_start_matches("build.target = ").trim_matches('"'); - - if !trimmed.starts_with('[') { - return Ok([trimmed.to_owned()].to_vec()); +fn parse_json_cargo_config_build_target( + config: &CargoConfigFile, +) -> anyhow::Result<Option<Vec<String>>> { + let target = config.get("build").and_then(|v| v.as_object()).and_then(|m| m.get("target")); + match target { + Some(serde_json::Value::String(s)) => Ok(Some(vec![s.to_owned()])), + Some(v) => serde_json::from_value(v.clone()) + .map(Option::Some) + .context("Failed to parse `build.target` as an array of target"), + // t`error: config value `build.target` is not set`, in which case we + // don't wanna log the error + None => Ok(None), } - - serde_json::from_str(trimmed).context("Failed to parse `build.target` as an array of target") } #[cfg(test)] @@ -90,7 +92,7 @@ mod tests { let sysroot = Sysroot::empty(); let manifest_path = ManifestPath::try_from(AbsPathBuf::assert(Utf8PathBuf::from(manifest_path))).unwrap(); - let cfg = QueryConfig::Cargo(&sysroot, &manifest_path); + let cfg = QueryConfig::Cargo(&sysroot, &manifest_path, &None); assert!(get(cfg, None, &FxHashMap::default()).is_ok()); } diff --git a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/version.rs b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/version.rs index 91ba8598591..357053d8e82 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/version.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/version.rs @@ -12,7 +12,7 @@ pub(crate) fn get( extra_env: &FxHashMap<String, Option<String>>, ) -> Result<Option<Version>, anyhow::Error> { let (mut cmd, prefix) = match config { - QueryConfig::Cargo(sysroot, cargo_toml) => { + QueryConfig::Cargo(sysroot, cargo_toml, _) => { (sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env), "cargo ") } QueryConfig::Rustc(sysroot, current_dir) => { @@ -44,7 +44,7 @@ mod tests { let sysroot = Sysroot::empty(); let manifest_path = ManifestPath::try_from(AbsPathBuf::assert(Utf8PathBuf::from(manifest_path))).unwrap(); - let cfg = QueryConfig::Cargo(&sysroot, &manifest_path); + let cfg = QueryConfig::Cargo(&sysroot, &manifest_path, &None); assert!(get(cfg, &FxHashMap::default()).is_ok()); } diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index 43db84b4fa3..677f29e3c60 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -25,11 +25,9 @@ use crate::{ ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, build_dependencies::BuildScriptOutput, - cargo_workspace::{CargoMetadataConfig, DepKind, PackageData, RustLibSource}, - env::{ - cargo_config_build_target_dir, cargo_config_env, inject_cargo_env, - inject_cargo_package_env, inject_rustc_tool_env, - }, + cargo_config_file, + cargo_workspace::{CargoMetadataConfig, DepKind, FetchMetadata, PackageData, RustLibSource}, + env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env}, project_json::{Crate, CrateArrayIdx}, sysroot::RustLibSrcWorkspace, toolchain_info::{QueryConfig, rustc_cfg, target_data_layout, target_tuple, version}, @@ -270,7 +268,9 @@ impl ProjectWorkspace { tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.rust_lib_src_root(), root = ?sysroot.root(), "Using sysroot"); progress("querying project metadata".to_owned()); - let toolchain_config = QueryConfig::Cargo(&sysroot, cargo_toml); + let config_file = cargo_config_file::read(cargo_toml, extra_env, &sysroot); + let config_file_ = config_file.clone(); + let toolchain_config = QueryConfig::Cargo(&sysroot, cargo_toml, &config_file_); let targets = target_tuple::get(toolchain_config, target.as_deref(), extra_env).unwrap_or_default(); let toolchain = version::get(toolchain_config, extra_env) @@ -282,10 +282,24 @@ impl ProjectWorkspace { .ok() .flatten(); + let fetch_metadata = FetchMetadata::new( + cargo_toml, + workspace_dir, + &CargoMetadataConfig { + features: features.clone(), + targets: targets.clone(), + extra_args: extra_args.clone(), + extra_env: extra_env.clone(), + toolchain_version: toolchain.clone(), + kind: "workspace", + }, + &sysroot, + *no_deps, + ); let target_dir = config .target_dir .clone() - .or_else(|| cargo_config_build_target_dir(cargo_toml, extra_env, &sysroot)) + .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone())) .unwrap_or_else(|| workspace_dir.join("target").into()); // We spawn a bunch of processes to query various information about the workspace's @@ -319,7 +333,7 @@ impl ProjectWorkspace { }; rustc_dir.and_then(|rustc_dir| { info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source"); - match CargoWorkspace::fetch_metadata( + match FetchMetadata::new( &rustc_dir, workspace_dir, &CargoMetadataConfig { @@ -327,15 +341,12 @@ impl ProjectWorkspace { targets: targets.clone(), extra_args: extra_args.clone(), extra_env: extra_env.clone(), - target_dir: target_dir.clone(), toolchain_version: toolchain.clone(), kind: "rustc-dev" }, &sysroot, *no_deps, - true, - progress, - ) { + ).exec(&target_dir, true, progress) { Ok((meta, _error)) => { let workspace = CargoWorkspace::new( meta, @@ -364,40 +375,22 @@ impl ProjectWorkspace { }) }); - let cargo_metadata = s.spawn(|| { - CargoWorkspace::fetch_metadata( - cargo_toml, - workspace_dir, - &CargoMetadataConfig { - features: features.clone(), - targets: targets.clone(), - extra_args: extra_args.clone(), - extra_env: extra_env.clone(), - target_dir: target_dir.clone(), - toolchain_version: toolchain.clone(), - kind: "workspace", - }, - &sysroot, - *no_deps, - false, - progress, - ) - }); + let cargo_metadata = s.spawn(|| fetch_metadata.exec(&target_dir, false, progress)); let loaded_sysroot = s.spawn(|| { sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( config, &targets, toolchain.clone(), - target_dir.clone(), )), config.no_deps, workspace_dir, + &target_dir, progress, ) }); let cargo_config_extra_env = - s.spawn(|| cargo_config_env(cargo_toml, extra_env, &sysroot)); + s.spawn(move || cargo_config_env(cargo_toml, &config_file)); thread::Result::Ok(( rustc_cfg.join()?, data_layout.join()?, @@ -476,9 +469,7 @@ impl ProjectWorkspace { let target_dir = config .target_dir .clone() - .or_else(|| { - cargo_config_build_target_dir(project_json.manifest()?, &config.extra_env, &sysroot) - }) + .or_else(|| cargo_target_dir(project_json.manifest()?, &config.extra_env, &sysroot)) .unwrap_or_else(|| project_root.join("target").into()); // We spawn a bunch of processes to query various information about the workspace's @@ -502,6 +493,7 @@ impl ProjectWorkspace { &RustSourceWorkspaceConfig::Json(*sysroot_project), config.no_deps, project_root, + &target_dir, progress, ) } else { @@ -510,10 +502,10 @@ impl ProjectWorkspace { config, &targets, toolchain.clone(), - target_dir, )), config.no_deps, project_root, + &target_dir, progress, ) } @@ -554,7 +546,8 @@ impl ProjectWorkspace { None => Sysroot::empty(), }; - let query_config = QueryConfig::Cargo(&sysroot, detached_file); + let config_file = cargo_config_file::read(detached_file, &config.extra_env, &sysroot); + let query_config = QueryConfig::Cargo(&sysroot, detached_file, &config_file); let toolchain = version::get(query_config, &config.extra_env).ok().flatten(); let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env) .unwrap_or_default(); @@ -563,7 +556,7 @@ impl ProjectWorkspace { let target_dir = config .target_dir .clone() - .or_else(|| cargo_config_build_target_dir(detached_file, &config.extra_env, &sysroot)) + .or_else(|| cargo_target_dir(detached_file, &config.extra_env, &sysroot)) .unwrap_or_else(|| dir.join("target").into()); let loaded_sysroot = sysroot.load_workspace( @@ -571,17 +564,17 @@ impl ProjectWorkspace { config, &targets, toolchain.clone(), - target_dir.clone(), )), config.no_deps, dir, + &target_dir, &|_| (), ); if let Some(loaded_sysroot) = loaded_sysroot { sysroot.set_workspace(loaded_sysroot); } - let cargo_script = CargoWorkspace::fetch_metadata( + let fetch_metadata = FetchMetadata::new( detached_file, dir, &CargoMetadataConfig { @@ -589,25 +582,26 @@ impl ProjectWorkspace { targets, extra_args: config.extra_args.clone(), extra_env: config.extra_env.clone(), - target_dir, toolchain_version: toolchain.clone(), kind: "detached-file", }, &sysroot, config.no_deps, - false, - &|_| (), - ) - .ok() - .map(|(ws, error)| { - let cargo_config_extra_env = - cargo_config_env(detached_file, &config.extra_env, &sysroot); - ( - CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false), - WorkspaceBuildScripts::default(), - error.map(Arc::new), - ) - }); + ); + let target_dir = config + .target_dir + .clone() + .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone())) + .unwrap_or_else(|| dir.join("target").into()); + let cargo_script = + fetch_metadata.exec(&target_dir, false, &|_| ()).ok().map(|(ws, error)| { + let cargo_config_extra_env = cargo_config_env(detached_file, &config_file); + ( + CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false), + WorkspaceBuildScripts::default(), + error.map(Arc::new), + ) + }); Ok(ProjectWorkspace { kind: ProjectWorkspaceKind::DetachedFile { @@ -1889,15 +1883,33 @@ fn sysroot_metadata_config( config: &CargoConfig, targets: &[String], toolchain_version: Option<Version>, - target_dir: Utf8PathBuf, ) -> CargoMetadataConfig { CargoMetadataConfig { features: Default::default(), targets: targets.to_vec(), extra_args: Default::default(), extra_env: config.extra_env.clone(), - target_dir, toolchain_version, kind: "sysroot", } } + +fn cargo_target_dir( + manifest: &ManifestPath, + extra_env: &FxHashMap<String, Option<String>>, + sysroot: &Sysroot, +) -> Option<Utf8PathBuf> { + let cargo = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env); + let mut meta = cargo_metadata::MetadataCommand::new(); + meta.cargo_path(cargo.get_program()); + meta.manifest_path(manifest); + // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve. + // So we can use it to get `target_directory` before copying lockfiles + let mut other_options = vec!["--no-deps".to_owned()]; + if manifest.is_rust_manifest() { + meta.env("RUSTC_BOOTSTRAP", "1"); + other_options.push("-Zscript".to_owned()); + } + meta.other_options(other_options); + meta.exec().map(|m| m.target_directory).ok() +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 0ee01982fea..fc89f486f84 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -796,7 +796,7 @@ impl flags::AnalysisStats { // region:expressions let (previous_exprs, previous_unknown, previous_partially_unknown) = (num_exprs, num_exprs_unknown, num_exprs_partially_unknown); - for (expr_id, _) in body.exprs.iter() { + for (expr_id, _) in body.exprs() { let ty = &inference_result[expr_id]; num_exprs += 1; let unknown_or_partial = if ty.is_unknown() { @@ -901,7 +901,7 @@ impl flags::AnalysisStats { // region:patterns let (previous_pats, previous_unknown, previous_partially_unknown) = (num_pats, num_pats_unknown, num_pats_partially_unknown); - for (pat_id, _) in body.pats.iter() { + for (pat_id, _) in body.pats() { let ty = &inference_result[pat_id]; num_pats += 1; let unknown_or_partial = if ty.is_unknown() { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index f97bf832442..30ac93fb6f8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -9,6 +9,7 @@ use hir::{ChangeWithProcMacros, Crate}; use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig}; use ide_db::base_db; use itertools::Either; +use paths::Utf8PathBuf; use profile::StopWatch; use project_model::toolchain_info::{QueryConfig, target_data_layout}; use project_model::{ @@ -79,6 +80,7 @@ impl Tester { &RustSourceWorkspaceConfig::default_cargo(), false, &path, + &Utf8PathBuf::default(), &|_| (), ); if let Some(loaded_sysroot) = loaded_sysroot { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 8a848fb848c..292be1d5315 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -911,7 +911,8 @@ pub(crate) fn folding_range( | FoldKind::Array | FoldKind::TraitAliases | FoldKind::ExternCrates - | FoldKind::MatchArm => None, + | FoldKind::MatchArm + | FoldKind::Function => None, }; let range = range(line_index, fold.range); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index 59073af983b..1b940c70da6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -880,7 +880,8 @@ fn main() {{}} #[test] fn diagnostics_dont_block_typing() { - if skip_slow_tests() { + if skip_slow_tests() || std::env::var("CI").is_ok() { + // FIXME: This test is failing too frequently (therefore we disable it on CI). return; } diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index 121d2e33243..a9288ecd6fa 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -92,6 +92,7 @@ impl fmt::Debug for ErasedFileAstId { Use, Impl, BlockExpr, + AsmExpr, Fixup, ); if f.alternate() { @@ -144,6 +145,10 @@ enum ErasedFileAstIdKind { Impl, /// Associated with [`BlockExprFileAstId`]. BlockExpr, + // `global_asm!()` is an item, so we need to give it an `AstId`. So we give to all inline asm + // because incrementality is not a problem, they will always be the only item in the macro file, + // and memory usage also not because they're rare. + AsmExpr, /// Keep this last. Root, } @@ -204,14 +209,17 @@ impl ErasedFileAstId { .or_else(|| extern_block_ast_id(node, index_map)) .or_else(|| use_ast_id(node, index_map)) .or_else(|| impl_ast_id(node, index_map)) + .or_else(|| asm_expr_ast_id(node, index_map)) } fn should_alloc(node: &SyntaxNode) -> bool { - should_alloc_has_name(node) - || should_alloc_assoc_item(node) - || ast::ExternBlock::can_cast(node.kind()) - || ast::Use::can_cast(node.kind()) - || ast::Impl::can_cast(node.kind()) + let kind = node.kind(); + should_alloc_has_name(kind) + || should_alloc_assoc_item(kind) + || ast::ExternBlock::can_cast(kind) + || ast::Use::can_cast(kind) + || ast::Impl::can_cast(kind) + || ast::AsmExpr::can_cast(kind) } #[inline] @@ -278,7 +286,6 @@ impl<N> FileAstId<N> { #[derive(Hash)] struct ErasedHasNameFileAstId<'a> { - kind: SyntaxKind, name: &'a str, } @@ -332,6 +339,19 @@ fn use_ast_id( } } +impl AstIdNode for ast::AsmExpr {} + +fn asm_expr_ast_id( + node: &SyntaxNode, + index_map: &mut ErasedAstIdNextIndexMap, +) -> Option<ErasedFileAstId> { + if ast::AsmExpr::can_cast(node.kind()) { + Some(index_map.new_id(ErasedFileAstIdKind::AsmExpr, ())) + } else { + None + } +} + impl AstIdNode for ast::Impl {} fn impl_ast_id( @@ -433,7 +453,6 @@ macro_rules! register_has_name_ast_id { )+ fn has_name_ast_id(node: &SyntaxNode, index_map: &mut ErasedAstIdNextIndexMap) -> Option<ErasedFileAstId> { - let kind = node.kind(); match_ast! { match node { $( @@ -441,7 +460,6 @@ macro_rules! register_has_name_ast_id { let name = node.$name_method(); let name = name.as_ref().map_or("", |it| it.text_non_mutable()); let result = ErasedHasNameFileAstId { - kind, name, }; Some(index_map.new_id(ErasedFileAstIdKind::$ident, result)) @@ -452,8 +470,7 @@ macro_rules! register_has_name_ast_id { } } - fn should_alloc_has_name(node: &SyntaxNode) -> bool { - let kind = node.kind(); + fn should_alloc_has_name(kind: SyntaxKind) -> bool { false $( || ast::$ident::can_cast(kind) )* } }; @@ -483,7 +500,6 @@ macro_rules! register_assoc_item_ast_id { index_map: &mut ErasedAstIdNextIndexMap, parent: Option<&ErasedFileAstId>, ) -> Option<ErasedFileAstId> { - let kind = node.kind(); match_ast! { match node { $( @@ -491,7 +507,6 @@ macro_rules! register_assoc_item_ast_id { let name = $name_callback(node); let name = name.as_ref().map_or("", |it| it.text_non_mutable()); let properties = ErasedHasNameFileAstId { - kind, name, }; let result = ErasedAssocItemFileAstId { @@ -506,8 +521,7 @@ macro_rules! register_assoc_item_ast_id { } } - fn should_alloc_assoc_item(node: &SyntaxNode) -> bool { - let kind = node.kind(); + fn should_alloc_assoc_item(kind: SyntaxKind) -> bool { false $( || ast::$ident::can_cast(kind) )* } }; diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index 3f439472337..4cbc88cfb5e 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -158,6 +158,7 @@ Item = | TypeAlias | Union | Use +| AsmExpr MacroRules = Attr* Visibility? @@ -409,7 +410,8 @@ OffsetOfExpr = // global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")" // format_string := STRING_LITERAL / RAW_STRING_LITERAL AsmExpr = - Attr* 'builtin' '#' 'asm' '(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')' + Attr* 'builtin' '#' ( 'asm' | 'global_asm' | 'naked_asm' ) + '(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')' // operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" AsmOperandExpr = in_expr:Expr ('=>' out_expr:Expr)? diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs index e60243f2c91..e902516471d 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs @@ -406,42 +406,6 @@ impl ast::WhereClause { } } -impl ast::TypeParam { - pub fn remove_default(&self) { - if let Some((eq, last)) = self - .syntax() - .children_with_tokens() - .find(|it| it.kind() == T![=]) - .zip(self.syntax().last_child_or_token()) - { - ted::remove_all(eq..=last); - - // remove any trailing ws - if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) { - last.detach(); - } - } - } -} - -impl ast::ConstParam { - pub fn remove_default(&self) { - if let Some((eq, last)) = self - .syntax() - .children_with_tokens() - .find(|it| it.kind() == T![=]) - .zip(self.syntax().last_child_or_token()) - { - ted::remove_all(eq..=last); - - // remove any trailing ws - if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) { - last.detach(); - } - } - } -} - pub trait Removable: AstNode { fn remove(&self); } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index 79a9f4da338..2b862465420 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -118,6 +118,14 @@ impl AsmExpr { pub fn asm_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![asm]) } #[inline] pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) } + #[inline] + pub fn global_asm_token(&self) -> Option<SyntaxToken> { + support::token(&self.syntax, T![global_asm]) + } + #[inline] + pub fn naked_asm_token(&self) -> Option<SyntaxToken> { + support::token(&self.syntax, T![naked_asm]) + } } pub struct AsmLabel { pub(crate) syntax: SyntaxNode, @@ -2087,6 +2095,7 @@ impl ast::HasAttrs for GenericParam {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Item { + AsmExpr(AsmExpr), Const(Const), Enum(Enum), ExternBlock(ExternBlock), @@ -2106,7 +2115,6 @@ pub enum Item { Use(Use), } impl ast::HasAttrs for Item {} -impl ast::HasDocComments for Item {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Pat { @@ -8409,6 +8417,10 @@ impl AstNode for GenericParam { } } } +impl From<AsmExpr> for Item { + #[inline] + fn from(node: AsmExpr) -> Item { Item::AsmExpr(node) } +} impl From<Const> for Item { #[inline] fn from(node: Const) -> Item { Item::Const(node) } @@ -8482,7 +8494,8 @@ impl AstNode for Item { fn can_cast(kind: SyntaxKind) -> bool { matches!( kind, - CONST + ASM_EXPR + | CONST | ENUM | EXTERN_BLOCK | EXTERN_CRATE @@ -8504,6 +8517,7 @@ impl AstNode for Item { #[inline] fn cast(syntax: SyntaxNode) -> Option<Self> { let res = match syntax.kind() { + ASM_EXPR => Item::AsmExpr(AsmExpr { syntax }), CONST => Item::Const(Const { syntax }), ENUM => Item::Enum(Enum { syntax }), EXTERN_BLOCK => Item::ExternBlock(ExternBlock { syntax }), @@ -8528,6 +8542,7 @@ impl AstNode for Item { #[inline] fn syntax(&self) -> &SyntaxNode { match self { + Item::AsmExpr(it) => &it.syntax, Item::Const(it) => &it.syntax, Item::Enum(it) => &it.syntax, Item::ExternBlock(it) => &it.syntax, diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index 309332873cb..d67f24fda96 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -680,7 +680,7 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::TupleEx let expr = elements.into_iter().format(", "); expr_from_text(&format!("({expr})")) } -pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { +pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::BinExpr { expr_from_text(&format!("{lhs} = {rhs}")) } fn expr_from_text<E: Into<ast::Expr> + AstNode>(text: &str) -> E { diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 17cc5f9c057..1ba61073151 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -440,6 +440,19 @@ impl SyntaxFactory { ast } + pub fn expr_assignment(&self, lhs: ast::Expr, rhs: ast::Expr) -> ast::BinExpr { + let ast = make::expr_assignment(lhs.clone(), rhs.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(lhs.syntax().clone(), ast.lhs().unwrap().syntax().clone()); + builder.map_node(rhs.syntax().clone(), ast.rhs().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + pub fn expr_bin(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::BinExpr { let ast::Expr::BinExpr(ast) = make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update() diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index e5d8f78d948..7b719b5dec7 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -1940,6 +1940,7 @@ pub mod prelude { clone::Clone, // :clone cmp::{Eq, PartialEq}, // :eq cmp::{Ord, PartialOrd}, // :ord + convert::AsMut, // :as_mut convert::AsRef, // :as_ref convert::{From, Into, TryFrom, TryInto}, // :from default::Default, // :default diff --git a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml index 35a5a4d82b2..1fc1da50a0a 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml +++ b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml @@ -16,6 +16,9 @@ crossbeam-channel.workspace = true [dev-dependencies] lsp-types = "=0.95" ctrlc = "3.4.7" +anyhow.workspace = true +rustc-hash.workspace = true +toolchain.workspace = true [lints] workspace = true diff --git a/src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs b/src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs deleted file mode 100644 index 6b3acda7bcd..00000000000 --- a/src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! A minimal example LSP server that can only respond to the `gotoDefinition` request. To use -//! this example, execute it and then send an `initialize` request. -//! -//! ```no_run -//! Content-Length: 85 -//! -//! {"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {"capabilities": {}}} -//! ``` -//! -//! This will respond with a server response. Then send it a `initialized` notification which will -//! have no response. -//! -//! ```no_run -//! Content-Length: 59 -//! -//! {"jsonrpc": "2.0", "method": "initialized", "params": {}} -//! ``` -//! -//! Once these two are sent, then we enter the main loop of the server. The only request this -//! example can handle is `gotoDefinition`: -//! -//! ```no_run -//! Content-Length: 159 -//! -//! {"jsonrpc": "2.0", "method": "textDocument/definition", "id": 2, "params": {"textDocument": {"uri": "file://temp"}, "position": {"line": 1, "character": 1}}} -//! ``` -//! -//! To finish up without errors, send a shutdown request: -//! -//! ```no_run -//! Content-Length: 67 -//! -//! {"jsonrpc": "2.0", "method": "shutdown", "id": 3, "params": null} -//! ``` -//! -//! The server will exit the main loop and finally we send a `shutdown` notification to stop -//! the server. -//! -//! ``` -//! Content-Length: 54 -//! -//! {"jsonrpc": "2.0", "method": "exit", "params": null} -//! ``` - -#![allow(clippy::print_stderr)] - -use std::error::Error; - -use lsp_types::OneOf; -use lsp_types::{ - GotoDefinitionResponse, InitializeParams, ServerCapabilities, request::GotoDefinition, -}; - -use lsp_server::{Connection, ExtractError, Message, Request, RequestId, Response}; - -fn main() -> Result<(), Box<dyn Error + Sync + Send>> { - // Note that we must have our logging only write out to stderr. - eprintln!("starting generic LSP server"); - - // Create the transport. Includes the stdio (stdin and stdout) versions but this could - // also be implemented to use sockets or HTTP. - let (connection, io_threads) = Connection::stdio(); - - // Run the server and wait for the two threads to end (typically by trigger LSP Exit event). - let server_capabilities = serde_json::to_value(&ServerCapabilities { - definition_provider: Some(OneOf::Left(true)), - ..Default::default() - }) - .unwrap(); - let initialization_params = match connection.initialize(server_capabilities) { - Ok(it) => it, - Err(e) => { - if e.channel_is_disconnected() { - io_threads.join()?; - } - return Err(e.into()); - } - }; - main_loop(connection, initialization_params)?; - io_threads.join()?; - - // Shut down gracefully. - eprintln!("shutting down server"); - Ok(()) -} - -fn main_loop( - connection: Connection, - params: serde_json::Value, -) -> Result<(), Box<dyn Error + Sync + Send>> { - let _params: InitializeParams = serde_json::from_value(params).unwrap(); - eprintln!("starting example main loop"); - for msg in &connection.receiver { - eprintln!("got msg: {msg:?}"); - match msg { - Message::Request(req) => { - if connection.handle_shutdown(&req)? { - return Ok(()); - } - eprintln!("got request: {req:?}"); - match cast::<GotoDefinition>(req) { - Ok((id, params)) => { - eprintln!("got gotoDefinition request #{id}: {params:?}"); - let result = Some(GotoDefinitionResponse::Array(Vec::new())); - let result = serde_json::to_value(&result).unwrap(); - let resp = Response { id, result: Some(result), error: None }; - connection.sender.send(Message::Response(resp))?; - continue; - } - Err(err @ ExtractError::JsonError { .. }) => panic!("{err:?}"), - Err(ExtractError::MethodMismatch(req)) => req, - }; - // ... - } - Message::Response(resp) => { - eprintln!("got response: {resp:?}"); - } - Message::Notification(not) => { - eprintln!("got notification: {not:?}"); - } - } - } - Ok(()) -} - -fn cast<R>(req: Request) -> Result<(RequestId, R::Params), ExtractError<Request>> -where - R: lsp_types::request::Request, - R::Params: serde::de::DeserializeOwned, -{ - req.extract(R::METHOD) -} diff --git a/src/tools/rust-analyzer/lib/lsp-server/examples/manual_test.sh b/src/tools/rust-analyzer/lib/lsp-server/examples/manual_test.sh new file mode 100755 index 00000000000..d028ac43301 --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/examples/manual_test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Simple nine-packet LSP test for examples/minimal_lsp.rs +# Usage (two tabs): +# +# mkfifo /tmp/lsp_pipe # one-time setup +# # tab 1 – run the server +# cat /tmp/lsp_pipe | cargo run --example minimal_lsp +# +# # tab 2 – fire the packets (this script) +# bash examples/manual_test.sh # blocks until server exits +# +# If you don’t use a second tab, run the script in the background: +# +# bash examples/manual_test.sh & # writer in background +# cat /tmp/lsp_pipe | cargo run --example minimal_lsp +# +# The script opens /tmp/lsp_pipe for writing (exec 3>) and sends each JSON +# packet with a correct Content-Length header. +# +# One-liner alternative (single terminal, no FIFO): +# +# cargo run --example minimal_lsp <<'EOF' +# … nine packets … +# EOF +# +# Both approaches feed identical bytes to minimal_lsp via stdin. + +set -eu +PIPE=${1:-/tmp/lsp_pipe} + +mkfifo -m 600 "$PIPE" 2>/dev/null || true # create once, ignore if exists + +# open write end so the fifo stays open +exec 3> "$PIPE" + +send() { + local body=$1 + local len=$(printf '%s' "$body" | wc -c) + printf 'Content-Length: %d\r\n\r\n%s' "$len" "$body" >&3 +} + +send '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}' +send '{"jsonrpc":"2.0","method":"initialized","params":{}}' +send '{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///tmp/foo.rs","languageId":"rust","version":1,"text":"fn main( ){println!(\"hi\") }"}}}' +send '{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"position":{"line":0,"character":0}}}' +send '{"jsonrpc":"2.0","id":3,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"position":{"line":0,"character":0}}}' +send '{"jsonrpc":"2.0","id":4,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"position":{"line":0,"character":0}}}' +send '{"jsonrpc":"2.0","id":5,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"options":{"tabSize":4,"insertSpaces":true}}}' +send '{"jsonrpc":"2.0","id":6,"method":"shutdown","params":null}' +send '{"jsonrpc":"2.0","method":"exit","params":null}' + +exec 3>&- +echo "Packets sent – watch the other terminal for responses." diff --git a/src/tools/rust-analyzer/lib/lsp-server/examples/minimal_lsp.rs b/src/tools/rust-analyzer/lib/lsp-server/examples/minimal_lsp.rs new file mode 100644 index 00000000000..5eef999e062 --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/examples/minimal_lsp.rs @@ -0,0 +1,335 @@ +//! Minimal Language‑Server‑Protocol example: **`minimal_lsp.rs`** +//! ============================================================= +//! +//! | ↔ / ← | LSP method | What the implementation does | +//! |-------|------------|------------------------------| +//! | ↔ | `initialize` / `initialized` | capability handshake | +//! | ← | `textDocument/publishDiagnostics` | pushes a dummy info diagnostic whenever the buffer changes | +//! | ← | `textDocument/definition` | echoes an empty location array so the jump works | +//! | ← | `textDocument/completion` | offers one hard‑coded item `HelloFromLSP` | +//! | ← | `textDocument/hover` | shows *Hello from minimal_lsp* markdown | +//! | ← | `textDocument/formatting` | pipes the doc through **rustfmt** and returns a full‑file edit | +//! +//! ### Quick start +//! ```bash +//! cd rust-analyzer/lib/lsp-server +//! cargo run --example minimal_lsp +//! ``` +//! +//! ### Minimal manual session (all nine packets) +//! ```no_run +//! # 1. initialize - server replies with capabilities +//! Content-Length: 85 + +//! {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}} +//! +//! # 2. initialized - no response expected +//! Content-Length: 59 + +//! {"jsonrpc":"2.0","method":"initialized","params":{}} +//! +//! # 3. didOpen - provide initial buffer text +//! Content-Length: 173 + +//! {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///tmp/foo.rs","languageId":"rust","version":1,"text":"fn main( ){println!(\"hi\") }"}}} +//! +//! # 4. completion - expect HelloFromLSP +//! Content-Length: 139 + +//! {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"position":{"line":0,"character":0}}} +//! +//! # 5. hover - expect markdown greeting +//! Content-Length: 135 + +//! {"jsonrpc":"2.0","id":3,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"position":{"line":0,"character":0}}} +//! +//! # 6. goto-definition - dummy empty array +//! Content-Length: 139 + +//! {"jsonrpc":"2.0","id":4,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"position":{"line":0,"character":0}}} +//! +//! # 7. formatting - rustfmt full document +//! Content-Length: 157 + +//! {"jsonrpc":"2.0","id":5,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///tmp/foo.rs"},"options":{"tabSize":4,"insertSpaces":true}}} +//! +//! # 8. shutdown request - server acks and prepares to exit +//! Content-Length: 67 + +//! {"jsonrpc":"2.0","id":6,"method":"shutdown","params":null} +//! +//! # 9. exit notification - terminates the server +//! Content-Length: 54 + +//! {"jsonrpc":"2.0","method":"exit","params":null} +//! ``` +//! + +use std::{error::Error, io::Write}; + +use rustc_hash::FxHashMap; // fast hash map +use std::process::Stdio; +use toolchain::command; // clippy-approved wrapper + +#[allow(clippy::print_stderr, clippy::disallowed_types, clippy::disallowed_methods)] +use anyhow::{Context, Result, anyhow, bail}; +use lsp_server::{Connection, Message, Request as ServerRequest, RequestId, Response}; +use lsp_types::notification::Notification as _; // for METHOD consts +use lsp_types::request::Request as _; +use lsp_types::{ + CompletionItem, + CompletionItemKind, + // capability helpers + CompletionOptions, + CompletionResponse, + Diagnostic, + DiagnosticSeverity, + DidChangeTextDocumentParams, + DidOpenTextDocumentParams, + DocumentFormattingParams, + Hover, + HoverContents, + HoverProviderCapability, + // core + InitializeParams, + MarkedString, + OneOf, + Position, + PublishDiagnosticsParams, + Range, + ServerCapabilities, + TextDocumentSyncCapability, + TextDocumentSyncKind, + TextEdit, + Url, + // notifications + notification::{DidChangeTextDocument, DidOpenTextDocument, PublishDiagnostics}, + // requests + request::{Completion, Formatting, GotoDefinition, HoverRequest}, +}; // for METHOD consts + +// ===================================================================== +// main +// ===================================================================== + +#[allow(clippy::print_stderr)] +fn main() -> std::result::Result<(), Box<dyn Error + Sync + Send>> { + log::error!("starting minimal_lsp"); + + // transport + let (connection, io_thread) = Connection::stdio(); + + // advertised capabilities + let caps = ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)), + completion_provider: Some(CompletionOptions::default()), + definition_provider: Some(OneOf::Left(true)), + hover_provider: Some(HoverProviderCapability::Simple(true)), + document_formatting_provider: Some(OneOf::Left(true)), + ..Default::default() + }; + let init_value = serde_json::json!({ + "capabilities": caps, + "offsetEncoding": ["utf-8"], + }); + + let init_params = connection.initialize(init_value)?; + main_loop(connection, init_params)?; + io_thread.join()?; + log::error!("shutting down server"); + Ok(()) +} + +// ===================================================================== +// event loop +// ===================================================================== + +fn main_loop( + connection: Connection, + params: serde_json::Value, +) -> std::result::Result<(), Box<dyn Error + Sync + Send>> { + let _init: InitializeParams = serde_json::from_value(params)?; + let mut docs: FxHashMap<Url, String> = FxHashMap::default(); + + for msg in &connection.receiver { + match msg { + Message::Request(req) => { + if connection.handle_shutdown(&req)? { + break; + } + if let Err(err) = handle_request(&connection, &req, &mut docs) { + log::error!("[lsp] request {} failed: {err}", &req.method); + } + } + Message::Notification(note) => { + if let Err(err) = handle_notification(&connection, ¬e, &mut docs) { + log::error!("[lsp] notification {} failed: {err}", note.method); + } + } + Message::Response(resp) => log::error!("[lsp] response: {resp:?}"), + } + } + Ok(()) +} + +// ===================================================================== +// notifications +// ===================================================================== + +fn handle_notification( + conn: &Connection, + note: &lsp_server::Notification, + docs: &mut FxHashMap<Url, String>, +) -> Result<()> { + match note.method.as_str() { + DidOpenTextDocument::METHOD => { + let p: DidOpenTextDocumentParams = serde_json::from_value(note.params.clone())?; + let uri = p.text_document.uri; + docs.insert(uri.clone(), p.text_document.text); + publish_dummy_diag(conn, &uri)?; + } + DidChangeTextDocument::METHOD => { + let p: DidChangeTextDocumentParams = serde_json::from_value(note.params.clone())?; + if let Some(change) = p.content_changes.into_iter().next() { + let uri = p.text_document.uri; + docs.insert(uri.clone(), change.text); + publish_dummy_diag(conn, &uri)?; + } + } + _ => {} + } + Ok(()) +} + +// ===================================================================== +// requests +// ===================================================================== + +fn handle_request( + conn: &Connection, + req: &ServerRequest, + docs: &mut FxHashMap<Url, String>, +) -> Result<()> { + match req.method.as_str() { + GotoDefinition::METHOD => { + send_ok(conn, req.id.clone(), &lsp_types::GotoDefinitionResponse::Array(Vec::new()))?; + } + Completion::METHOD => { + let item = CompletionItem { + label: "HelloFromLSP".into(), + kind: Some(CompletionItemKind::FUNCTION), + detail: Some("dummy completion".into()), + ..Default::default() + }; + send_ok(conn, req.id.clone(), &CompletionResponse::Array(vec![item]))?; + } + HoverRequest::METHOD => { + let hover = Hover { + contents: HoverContents::Scalar(MarkedString::String( + "Hello from *minimal_lsp*".into(), + )), + range: None, + }; + send_ok(conn, req.id.clone(), &hover)?; + } + Formatting::METHOD => { + let p: DocumentFormattingParams = serde_json::from_value(req.params.clone())?; + let uri = p.text_document.uri; + let text = docs + .get(&uri) + .ok_or_else(|| anyhow!("document not in cache – did you send DidOpen?"))?; + let formatted = run_rustfmt(text)?; + let edit = TextEdit { range: full_range(text), new_text: formatted }; + send_ok(conn, req.id.clone(), &vec![edit])?; + } + _ => send_err( + conn, + req.id.clone(), + lsp_server::ErrorCode::MethodNotFound, + "unhandled method", + )?, + } + Ok(()) +} + +// ===================================================================== +// diagnostics +// ===================================================================== +fn publish_dummy_diag(conn: &Connection, uri: &Url) -> Result<()> { + let diag = Diagnostic { + range: Range::new(Position::new(0, 0), Position::new(0, 1)), + severity: Some(DiagnosticSeverity::INFORMATION), + code: None, + code_description: None, + source: Some("minimal_lsp".into()), + message: "dummy diagnostic".into(), + related_information: None, + tags: None, + data: None, + }; + let params = + PublishDiagnosticsParams { uri: uri.clone(), diagnostics: vec![diag], version: None }; + conn.sender.send(Message::Notification(lsp_server::Notification::new( + PublishDiagnostics::METHOD.to_owned(), + params, + )))?; + Ok(()) +} + +// ===================================================================== +// helpers +// ===================================================================== + +fn run_rustfmt(input: &str) -> Result<String> { + let cwd = std::env::current_dir().expect("can't determine CWD"); + let mut child = command("rustfmt", &cwd, &FxHashMap::default()) + .arg("--emit") + .arg("stdout") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("failed to spawn rustfmt – is it installed?")?; + + let Some(stdin) = child.stdin.as_mut() else { + bail!("stdin unavailable"); + }; + stdin.write_all(input.as_bytes())?; + let output = child.wait_with_output()?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("rustfmt failed: {stderr}"); + } + Ok(String::from_utf8(output.stdout)?) +} + +fn full_range(text: &str) -> Range { + let last_line_idx = text.lines().count().saturating_sub(1) as u32; + let last_col = text.lines().last().map_or(0, |l| l.chars().count()) as u32; + Range::new(Position::new(0, 0), Position::new(last_line_idx, last_col)) +} + +fn send_ok<T: serde::Serialize>(conn: &Connection, id: RequestId, result: &T) -> Result<()> { + let resp = Response { id, result: Some(serde_json::to_value(result)?), error: None }; + conn.sender.send(Message::Response(resp))?; + Ok(()) +} + +fn send_err( + conn: &Connection, + id: RequestId, + code: lsp_server::ErrorCode, + msg: &str, +) -> Result<()> { + let resp = Response { + id, + result: None, + error: Some(lsp_server::ResponseError { + code: code as i32, + message: msg.into(), + data: None, + }), + }; + conn.sender.send(Message::Response(resp))?; + Ok(()) +} diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 902793225ea..57ff326ce5a 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -ad3b7257615c28aaf8212a189ec032b8af75de51 +a9fb6103b05c6ad6eee6bed4c0bb5a2e8e1024c6 diff --git a/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs b/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs index d8cbf894520..b9f570fe0e3 100644 --- a/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs +++ b/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs @@ -116,6 +116,8 @@ const CONTEXTUAL_KEYWORDS: &[&str] = // keywords we use for special macro expansions const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[ "asm", + "naked_asm", + "global_asm", "att_syntax", "builtin", "clobber_abi", diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 8b57db23d01..77414bec82d 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -1021,7 +1021,6 @@ ui/foreign/issue-91370-foreign-fn-block-impl.rs ui/foreign/issue-99276-same-type-lifetimes.rs ui/function-pointer/issue-102289.rs ui/functions-closures/closure-expected-type/issue-38714.rs -ui/generic-associated-types/bugs/issue-100013.rs ui/generic-associated-types/bugs/issue-80626.rs ui/generic-associated-types/bugs/issue-87735.rs ui/generic-associated-types/bugs/issue-87755.rs @@ -1099,7 +1098,6 @@ ui/generic-associated-types/issue-90729.rs ui/generic-associated-types/issue-91139.rs ui/generic-associated-types/issue-91883.rs ui/generic-associated-types/issue-92033.rs -ui/generic-associated-types/issue-92096.rs ui/generic-associated-types/issue-92280.rs ui/generic-associated-types/issue-92954.rs ui/generic-associated-types/issue-93141.rs |
