From 746c87243ae5a440395f0f2008247bdfafb2f62e Mon Sep 17 00:00:00 2001 From: joonas-001 Date: Tue, 25 Aug 2026 19:49:23 +0800 Subject: [PATCH 01/76] Add performance notes for the floating-point round method --- library/std/src/num/f32.rs | 5 +++++ library/std/src/num/f64.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs index 385533748cb5d..09c2e6329831f 100644 --- a/library/std/src/num/f32.rs +++ b/library/std/src/num/f32.rs @@ -80,6 +80,11 @@ impl f32 { /// /// This function always returns the precise result. /// + /// On most hardware platforms, [`round_ties_even`](Self::round_ties_even) may execute faster + /// than `round`. If both rounding methods fit the use case, consider using `round_ties_even`. + /// Note that the two methods apply different rounding rules to values exactly halfway between + /// two integers. + /// /// # Examples /// /// ``` diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs index 66482a8b654a5..7a813456723a3 100644 --- a/library/std/src/num/f64.rs +++ b/library/std/src/num/f64.rs @@ -80,6 +80,11 @@ impl f64 { /// /// This function always returns the precise result. /// + /// On most hardware platforms, [`round_ties_even`](Self::round_ties_even) may execute faster + /// than `round`. If both rounding methods fit the use case, consider using `round_ties_even`. + /// Note that the two methods apply different rounding rules to values exactly halfway between + /// two integers. + /// /// # Examples /// /// ``` From c447e2e61701796fdf3adefcf3fb6d2d65e36d5a Mon Sep 17 00:00:00 2001 From: jyn Date: Sat, 29 Aug 2026 10:58:59 +0200 Subject: [PATCH 02/76] open issues when scheduled linkcheck fails - identify issues by label and title - reuse an existing issue if it already exists - use a bash script around `gh` so we don't add unvetted dependencies - add tests for the bash script using a fake `gh` mock --- .../rustc-dev-guide/.github/workflows/ci.yml | 35 +++++++++- .../ci/close-scheduled-linkcheck-issues.sh | 31 +++++++++ .../ci/report-scheduled-linkcheck-failure.sh | 51 ++++++++++++++ src/doc/rustc-dev-guide/ci/tests/fakes/gh | 25 +++++++ .../report-scheduled-linkcheck-failure.sh | 69 +++++++++++++++++++ 5 files changed, 210 insertions(+), 1 deletion(-) create mode 100755 src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh create mode 100755 src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh create mode 100755 src/doc/rustc-dev-guide/ci/tests/fakes/gh create mode 100755 src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 4108ef8c2e1e2..fd5515dda0e39 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -22,11 +22,14 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # linkcheck needs the base commit. fetch-depth: 0 + - name: Test CI scripts + run: ci/tests/report-scheduled-linkcheck-failure.sh + - name: Cache binaries id: mdbook-cache uses: actions/cache@v4 @@ -88,3 +91,33 @@ jobs: run: | # using split_inclusive that uses regex feature that uses an unstable feature RUSTC_BOOTSTRAP=1 cargo run --release --manifest-path ci/sembr/Cargo.toml src + + notify-scheduled-failure: + name: Open an issue if links are broken + needs: ci + if: failure() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + actions: read + issues: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Open an issue or comment on an existing one + run: ci/report-scheduled-linkcheck-failure.sh + env: + GH_TOKEN: ${{ github.token }} + + close-scheduled-failure: + name: Close linkcheck issue if no links are broken + needs: ci + if: success() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@v7 + - name: Close issue + run: ci/close-scheduled-linkcheck-issues.sh + env: + GH_TOKEN: ${{ github.token }} diff --git a/src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh b/src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh new file mode 100755 index 0000000000000..7fb4c74cc128c --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}" +: "${GITHUB_RUN_ID:?GITHUB_RUN_ID must be set}" +: "${GITHUB_SERVER_URL:?GITHUB_SERVER_URL must be set}" +: "${GH_TOKEN:?GH_TOKEN must be set}" + +successful_run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + +issue_numbers=$(gh issue list \ + --repo "$GITHUB_REPOSITORY" \ + --state open \ + --label C-broken-links \ + --label C-CI \ + --label A-linkcheck \ + --limit 100 \ + --json number,title \ + --jq '.[] | select(.title == "[automation] Dead links found") | .number') + +if [[ -z "$issue_numbers" ]]; then + echo "No scheduled linkcheck failure issue is open." + exit 0 +fi + +while read -r issue_number; do + gh issue close "$issue_number" \ + --repo "$GITHUB_REPOSITORY" \ + --comment "The scheduled link check is succeeding again: $successful_run_url" +done <<< "$issue_numbers" diff --git a/src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh b/src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh new file mode 100755 index 0000000000000..178b3b40a75bf --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}" +: "${GITHUB_RUN_ID:?GITHUB_RUN_ID must be set}" +: "${GITHUB_RUN_NUMBER:?GITHUB_RUN_NUMBER must be set}" +: "${GITHUB_SERVER_URL:?GITHUB_SERVER_URL must be set}" +: "${GITHUB_SHA:?GITHUB_SHA must be set}" +: "${GH_TOKEN:?GH_TOKEN must be set}" + +title="[automation] Dead links found" +job_url=$(gh api \ + "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs" \ + --jq '.jobs[] | select(.name == "ci") | .html_url') + +issue_number=$(gh issue list \ + --repo "$GITHUB_REPOSITORY" \ + --state open \ + --label C-broken-links \ + --label C-CI \ + --label A-linkcheck \ + --limit 100 \ + --json number,title \ + --jq '[.[] | select(.title == "[automation] Dead links found")][0].number // empty') + +if [[ -n "$issue_number" ]]; then + gh issue comment "$issue_number" \ + --repo "$GITHUB_REPOSITORY" \ + --body "The scheduled link check failed again in [CI run #$GITHUB_RUN_NUMBER]($job_url)." + exit 0 +fi + +body=$(cat <' "$@" + printf '\n' +} >> "$GH_MOCK_LOG" + +case "$command" in + api) + if [[ ${GH_MOCK_API_FAIL-} == 1 ]]; then + exit 1 + fi + echo "https://github.example/jobs/123" + ;; + issue) + if [[ ${1-} == list && -n ${GH_MOCK_ISSUE_NUMBER-} ]]; then + echo "$GH_MOCK_ISSUE_NUMBER" + fi + ;; +esac diff --git a/src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh b/src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh new file mode 100755 index 0000000000000..96526ef0ea350 --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +script="$repo_root/ci/report-scheduled-linkcheck-failure.sh" +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +export PATH="$repo_root/ci/tests/fakes:$PATH" +export GH_MOCK_LOG="$tmp/gh.log" +export GITHUB_REPOSITORY="rust-lang/rustc-dev-guide" +export GITHUB_RUN_ID=123 +export GITHUB_RUN_NUMBER=456 +export GITHUB_SERVER_URL="https://github.com" +export GITHUB_SHA=0123456789abcdef +export GH_TOKEN=test-token + +fail() { + echo "error: $*" >&2 + exit 1 +} + +assert_log_contains() { + grep -F -- "$1" "$GH_MOCK_LOG" >/dev/null || fail "gh log does not contain: $1" +} + +assert_log_excludes() { + if grep -F -- "$1" "$GH_MOCK_LOG" >/dev/null; then + fail "gh log unexpectedly contains: $1" + fi +} + +# A first failure creates the issue with all labels and links to the failed job. +: > "$GH_MOCK_LOG" +unset GH_MOCK_ISSUE_NUMBER GH_MOCK_API_FAIL +"$script" >/dev/null +assert_log_contains 'issue <--repo> <--state> ' +assert_log_contains '<--label> <--label> <--label> ' +assert_log_contains '<--json> <--jq> <[.[] | select(.title == "[automation] Dead links found")][0].number // empty>' +assert_log_contains 'issue ' +assert_log_contains '<--title> <[automation] Dead links found>' +assert_log_contains '<--label> <--label> <--label> ' +assert_log_contains '[CI run #456](https://github.example/jobs/123)' +assert_log_excludes 'issue ' + +# A later failure comments on the existing issue instead of creating another. +: > "$GH_MOCK_LOG" +export GH_MOCK_ISSUE_NUMBER=42 +"$script" >/dev/null +assert_log_contains 'issue <42>' +assert_log_contains 'failed again in [CI run #456](https://github.example/jobs/123)' +assert_log_excludes 'issue ' + +# GitHub API errors and missing required environment variables remain fatal. +: > "$GH_MOCK_LOG" +export GH_MOCK_API_FAIL=1 +if "$script" >/dev/null 2>&1; then + fail "script succeeded after gh api failed" +fi +unset GH_MOCK_API_FAIL + +for variable in GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_SERVER_URL GITHUB_SHA GH_TOKEN; do + if env -u "$variable" "$script" >/dev/null 2>&1; then + fail "script succeeded without $variable" + fi +done + +echo "report-scheduled-linkcheck-failure tests passed" From 9565cedf8500609e975b88f30220c4a187c3d0a8 Mon Sep 17 00:00:00 2001 From: Yukang Date: Mon, 7 Sep 2026 13:53:24 +0800 Subject: [PATCH 03/76] Add regression test for importing block-scoped traits --- .../block-scoped-trait-import-issue-134146.rs | 59 ++++++++++++++++++ ...ck-scoped-trait-import-issue-134146.stderr | 62 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 tests/ui/traits/block-scoped-trait-import-issue-134146.rs create mode 100644 tests/ui/traits/block-scoped-trait-import-issue-134146.stderr diff --git a/tests/ui/traits/block-scoped-trait-import-issue-134146.rs b/tests/ui/traits/block-scoped-trait-import-issue-134146.rs new file mode 100644 index 0000000000000..0a9874c281d67 --- /dev/null +++ b/tests/ui/traits/block-scoped-trait-import-issue-134146.rs @@ -0,0 +1,59 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/134146. +//! Traits declared inside bodies cannot be imported through their enclosing function or const. + +#![allow(dead_code)] + +fn main() { + { + trait Hello { + fn hello(&self) { + println!("hello world"); + } + } + impl Hello for T {} + } + + ().hello(); + //~^ ERROR no method named `hello` found +} + +fn nested_module() { + mod inner { + pub trait Nested { + fn nested(&self) {} + } + impl Nested for () {} + } +} + +fn use_nested() { + ().nested(); + //~^ ERROR no method named `nested` found +} + +const _: () = { + trait InConst { + fn in_const(&self) {} + } + impl InConst for () {} +}; + +fn use_const() { + ().in_const(); + //~^ ERROR no method named `in_const` found +} + +fn multiple_candidates() { + { + trait First { + fn several(&self) {} + } + trait Second { + fn several(&self) {} + } + impl First for () {} + impl Second for () {} + } + ().several(); + //~^ ERROR no method named `several` found +} diff --git a/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr b/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr new file mode 100644 index 0000000000000..dfe305bf9853b --- /dev/null +++ b/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr @@ -0,0 +1,62 @@ +error[E0599]: no method named `hello` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:16:8 + | +LL | fn hello(&self) { + | ----- the method is available for `()` here +... +LL | ().hello(); + | ^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope +help: trait `Hello` which provides `hello` is implemented but not in scope; perhaps you want to import it + | +LL + use main::Hello; + | + +error[E0599]: no method named `nested` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:30:8 + | +LL | fn nested(&self) {} + | ------ the method is available for `()` here +... +LL | ().nested(); + | ^^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope +help: trait `Nested` which provides `nested` is implemented but not in scope; perhaps you want to import it + | +LL + use nested_module::inner::Nested; + | + +error[E0599]: no method named `in_const` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:42:8 + | +LL | fn in_const(&self) {} + | -------- the method is available for `()` here +... +LL | ().in_const(); + | ^^^^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope +help: trait `InConst` which provides `in_const` is implemented but not in scope; perhaps you want to import it + | +LL + use _::InConst; + | + +error[E0599]: no method named `several` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:57:8 + | +LL | ().several(); + | ^^^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope +help: the following traits which provide `several` are implemented but not in scope; perhaps you want to import one of them + | +LL + use multiple_candidates::First; + | +LL + use multiple_candidates::Second; + | + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0599`. From c9e2b6709dabbc42d2a6214c32b4574986815152 Mon Sep 17 00:00:00 2001 From: Yukang Date: Mon, 7 Sep 2026 14:04:46 +0800 Subject: [PATCH 04/76] Avoid suggesting imports of traits declared inside bodies --- .../rustc_hir_typeck/src/method/suggest.rs | 13 +++++----- ...ck-scoped-trait-import-issue-134146.stderr | 24 +++++-------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index e59ca32aba116..349e6596bc4b0 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -426,12 +426,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )); } else { msg += &format!(" but {} not reachable", pluralize!("is", suggs.len())); - err.span_suggestions( - span, - msg, - suggs, - Applicability::MaybeIncorrect, - ); + err.help(format!("{msg}:\n{}", suggs.join("").trim_end())); } }; if accessible_sugg.is_empty() { @@ -4002,6 +3997,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { candidates.into_iter().partition(|id| { let vis = self.tcx.visibility(*id); vis.is_accessible_from(scope, self.tcx) + // Visibility alone does not make `fn_name::Trait` an importable path. + // We need to make sure all parent are modules, otherwise the path is not importable. + && std::iter::successors(self.tcx.opt_parent(*id), |&id| self.tcx.opt_parent(id)) + .all(|id| self.tcx.def_kind(id) == DefKind::Mod) }); let sugg = |candidates: Vec<_>, visible| { @@ -4115,7 +4114,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if suggs.len() == 1 { err.help(msg); } else { - err.span_suggestions(span, msg, suggs, Applicability::MaybeIncorrect); + err.help(format!("{msg}:\n{}", suggs.join("").trim_end())); } }; if accessible_sugg.is_empty() { diff --git a/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr b/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr index dfe305bf9853b..41827953bb796 100644 --- a/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr +++ b/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr @@ -8,10 +8,7 @@ LL | ().hello(); | ^^^^^ method not found in `()` | = help: items from traits can only be used if the trait is in scope -help: trait `Hello` which provides `hello` is implemented but not in scope; perhaps you want to import it - | -LL + use main::Hello; - | + = help: trait `main::Hello` which provides `hello` is implemented but not reachable error[E0599]: no method named `nested` found for unit type `()` in the current scope --> $DIR/block-scoped-trait-import-issue-134146.rs:30:8 @@ -23,10 +20,7 @@ LL | ().nested(); | ^^^^^^ method not found in `()` | = help: items from traits can only be used if the trait is in scope -help: trait `Nested` which provides `nested` is implemented but not in scope; perhaps you want to import it - | -LL + use nested_module::inner::Nested; - | + = help: trait `nested_module::inner::Nested` which provides `nested` is implemented but not reachable error[E0599]: no method named `in_const` found for unit type `()` in the current scope --> $DIR/block-scoped-trait-import-issue-134146.rs:42:8 @@ -38,10 +32,7 @@ LL | ().in_const(); | ^^^^^^^^ method not found in `()` | = help: items from traits can only be used if the trait is in scope -help: trait `InConst` which provides `in_const` is implemented but not in scope; perhaps you want to import it - | -LL + use _::InConst; - | + = help: trait `_::InConst` which provides `in_const` is implemented but not reachable error[E0599]: no method named `several` found for unit type `()` in the current scope --> $DIR/block-scoped-trait-import-issue-134146.rs:57:8 @@ -50,12 +41,9 @@ LL | ().several(); | ^^^^^^^ method not found in `()` | = help: items from traits can only be used if the trait is in scope -help: the following traits which provide `several` are implemented but not in scope; perhaps you want to import one of them - | -LL + use multiple_candidates::First; - | -LL + use multiple_candidates::Second; - | + = help: the following traits which provide `several` are implemented but not reachable: + multiple_candidates::First + multiple_candidates::Second error: aborting due to 4 previous errors From c18476f93b4096d23e3a2aba19cdb62b0a8b0f20 Mon Sep 17 00:00:00 2001 From: 000wahab000 Date: Wed, 9 Sep 2026 01:35:48 +0530 Subject: [PATCH 05/76] fix: typo in thir.md --- src/doc/rustc-dev-guide/src/thir.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/thir.md b/src/doc/rustc-dev-guide/src/thir.md index 1ba30d86a6913..8b80d0be16874 100644 --- a/src/doc/rustc-dev-guide/src/thir.md +++ b/src/doc/rustc-dev-guide/src/thir.md @@ -136,7 +136,7 @@ Thir { kind: Scope { region_scope: Node(5), hir_id: HirId(DefId(0:3 ~ main[26fd]::main).5), - // reference to expression 0 above + // reference to expression 2 above value: e2, }, ty: i32, From 78784985c4cb67c5c79dbb2f88b05f5735730423 Mon Sep 17 00:00:00 2001 From: jyn Date: Thu, 10 Sep 2026 17:12:31 +0200 Subject: [PATCH 06/76] address review comments --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index fd5515dda0e39..606d388d1238b 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -99,6 +99,7 @@ jobs: runs-on: ubuntu-latest permissions: actions: read + contents: read issues: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -116,7 +117,7 @@ jobs: contents: read issues: write steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Close issue run: ci/close-scheduled-linkcheck-issues.sh env: From 4aee1dc1493dcd3015e11c0ab4b8cf396ed999df Mon Sep 17 00:00:00 2001 From: supercooltest2024 Date: Thu, 10 Sep 2026 11:45:25 -0600 Subject: [PATCH 07/76] spelling --- src/doc/rustc-dev-guide/src/bug-fix-procedure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/bug-fix-procedure.md b/src/doc/rustc-dev-guide/src/bug-fix-procedure.md index 0db0e07d4e7be..5416e361e5d24 100644 --- a/src/doc/rustc-dev-guide/src/bug-fix-procedure.md +++ b/src/doc/rustc-dev-guide/src/bug-fix-procedure.md @@ -101,7 +101,7 @@ declare_lint! { }, } -// 2. Add a decidacted lint pass for it. +// 2. Add a dedicated lint pass for it. // This step can be skipped if you emit the lint as part of an existing pass. #[derive(Default)] From fe4736508d52919f2a87f44b39c65b8fbafbded8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 11 Sep 2026 20:27:57 +0200 Subject: [PATCH 08/76] Add the alternatives parts in `Cache::paths` --- src/librustdoc/formats/cache.rs | 54 +++++++++++++++++----- src/librustdoc/html/format.rs | 22 +++++---- src/librustdoc/html/render/context.rs | 12 ++--- src/librustdoc/html/render/print_item.rs | 8 ++-- src/librustdoc/html/render/search_index.rs | 28 +++++++---- src/librustdoc/html/render/write_shared.rs | 17 +++++-- src/librustdoc/json/mod.rs | 10 ++-- 7 files changed, 104 insertions(+), 47 deletions(-) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index ccee062584e01..3dee84c2f4dc4 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -18,6 +18,27 @@ use crate::formats::item_type::ItemType; use crate::html::render::{IndexItem, IndexItemInfo}; use crate::visit_lib::RustdocEffectiveVisibilities; +pub(crate) struct PathInfo { + /// Parts of the path. So in `foo::bar::bib`, it will be `["foo", "bar", "bib"]`. + pub(crate) parts: Vec, + pub(crate) ty: ItemType, + /// When a reexport inline an item, we can end up with the same `DefId` with multiple local + /// targets. So in case like: + /// + /// ``` + /// /// Link to [`a2`]. + /// pub use std::ffi::os_str::OsString as a1; + /// /// Link to [`a1`]. + /// pub use std::ffi::os_str::OsString as a2; + /// /// Link to [`a2`]. + /// pub use std::ffi::os_str::OsString as a3; + /// ``` + /// + /// To ensure that `a1` and `a2` links to `a1` and `a2` which have the same `DefId`, we need + /// to store both paths. + pub(crate) alternatives: Vec>, +} + /// This cache is used to store information about the [`clean::Crate`] being /// rendered in order to provide more useful documentation. This contains /// information like all implementors of a trait, all traits a type implements, @@ -42,7 +63,7 @@ pub(crate) struct Cache { /// URLs when a type is being linked to. External paths are not located in /// this map because the `External` type itself has all the information /// necessary. - pub(crate) paths: FxIndexMap, ItemType)>, + pub(crate) paths: FxIndexMap, /// Similar to `paths`, but only holds external paths. This is only used for /// generating explicit hyperlinks to other crates. @@ -358,7 +379,8 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::ForeignTypeItem | clean::MacroItem(..) | clean::ProcMacroItem(..) - | clean::VariantItem(..) => { + | clean::VariantItem(..) + | clean::PrimitiveItem(..) => { use rustc_data_structures::fx::IndexEntry as Entry; let skip_because_unstable = matches!( @@ -376,21 +398,31 @@ impl DocFolder for CacheBuilder<'_, '_> { let item_def_id = item.item_id.expect_def_id(); match self.cache.paths.entry(item_def_id) { Entry::Vacant(entry) => { - entry.insert((self.cache.stack.clone(), item.type_())); + entry.insert(PathInfo { + parts: self.cache.stack.clone(), + ty: item.type_(), + alternatives: Vec::new(), + }); } Entry::Occupied(mut entry) => { - if entry.get().0.len() > self.cache.stack.len() { - entry.insert((self.cache.stack.clone(), item.type_())); + // Shorter paths are preferred by default. + if entry.get().parts.len() > self.cache.stack.len() { + let old_parts = std::mem::replace( + &mut entry.get_mut().parts, + self.cache.stack.clone(), + ); + // We only keep the old path if it's a different (final) name. + if old_parts.last() != self.cache.stack.last() { + entry.get_mut().alternatives.push(old_parts); + } + } + if !entry.get().alternatives.contains(&self.cache.stack) { + entry.get_mut().alternatives.push(self.cache.stack.clone()); } } } } } - clean::PrimitiveItem(..) => { - self.cache - .paths - .insert(item.item_id.expect_def_id(), (self.cache.stack.clone(), item.type_())); - } clean::ExternCrateItem { .. } | clean::ImportItem(..) @@ -570,7 +602,7 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It // in a field of the cache whose elements are added to the search index later, // after cache building is complete (see `handle_orphan_impl_child`). match cache.paths.get(&parent_did) { - Some((fqp, _)) => (Some(parent_did), &fqp[..fqp.len() - 1]), + Some(info) => (Some(parent_did), &info.parts[..info.parts.len() - 1]), None => { handle_orphan_impl_child(cache, item, parent_did); return; diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 13fc6e3bc1bb8..07b0f02b7d5b8 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -564,7 +564,7 @@ pub(crate) fn href_with_root_path( } _ => original_did, }; - if is_unnamable(cx.tcx(), did) { + if is_unnamable(tcx, did) { return Err(HrefError::UnnamableItem); } let cache = cx.cache(); @@ -586,12 +586,12 @@ pub(crate) fn href_with_root_path( } let (fqp, shortty, url_parts, is_absolute) = match cache.paths.get(&did) { - Some(&(ref fqp, shortty)) => ( - fqp, - shortty, + Some(info) => ( + &info.parts, + info.ty, { - let module_fqp = to_module_fqp(shortty, fqp.as_slice()); - debug!(?fqp, ?shortty, ?module_fqp); + let module_fqp = to_module_fqp(info.ty, info.parts.as_slice()); + debug!(?info.parts, ?info.ty, ?module_fqp); href_relative_parts(module_fqp, relative_to) }, false, @@ -663,11 +663,15 @@ pub(crate) fn link_tooltip( ) -> impl fmt::Display { fmt::from_fn(move |f| { let cache = cx.cache(); - let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) + let Some((fqp, shortty)) = cache + .paths + .get(&did) + .map(|info| (&info.parts, info.ty)) + .or_else(|| cache.external_paths.get(&did).map(|(fqp, shortty)| (fqp, *shortty))) else { return Ok(()); }; - let fqp = if *shortty == ItemType::Primitive { + let fqp = if shortty == ItemType::Primitive { // primitives are documented in a crate, but not actually part of it slice::from_ref(fqp.last().unwrap()) } else { @@ -679,7 +683,7 @@ pub(crate) fn link_tooltip( for component in fqp { write!(f, "{component}::")?; } - if *shortty == ItemType::Enum && tcx.def_kind(id) == DefKind::Field { + if shortty == ItemType::Enum && tcx.def_kind(id) == DefKind::Field { write!(f, "{}::", tcx.item_name(tcx.parent(id)))?; } write!(f, "{}", tcx.item_name(id))?; diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 56dd665177a93..d00b705d3766c 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -296,19 +296,19 @@ impl<'tcx> Context<'tcx> { &self.shared.style_files, ) } else { - if let Some(&(ref names, ty)) = self.cache().paths.get(&it.item_id.expect_def_id()) - && (self.current.len() + 1 != names.len() - || self.current.iter().zip(names.iter()).any(|(a, b)| a != b)) + if let Some(info) = self.cache().paths.get(&it.item_id.expect_def_id()) + && (self.current.len() + 1 != info.parts.len() + || self.current.iter().zip(info.parts.iter()).any(|(a, b)| a != b)) { // We checked that the redirection isn't pointing to the current file, // preventing an infinite redirection loop in the generated // documentation. let path = fmt::from_fn(|f| { - for name in &names[..names.len() - 1] { + for name in &info.parts[..info.parts.len() - 1] { write!(f, "{name}/")?; } - write!(f, "{}", print_ty_path(ty, names.last().unwrap().as_str())) + write!(f, "{}", print_ty_path(info.ty, info.parts.last().unwrap().as_str())) }); match self.shared.redirections { Some(ref redirections) => { @@ -320,7 +320,7 @@ impl<'tcx> Context<'tcx> { let _ = write!( current_path, "{}", - print_ty_path(ty, names.last().unwrap().as_str()) + print_ty_path(info.ty, info.parts.last().unwrap().as_str()) ); redirections.borrow_mut().insert(current_path, path.to_string()); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6f66dcf9eae83..46dba0253766a 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1455,12 +1455,12 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) -> // [^115718]: https://github.com/rust-lang/rust/issues/115718 let cache = &cx.shared.cache; if let Some(target_did) = t.type_.def_id(cache) - && let get_extern = { || cache.external_paths.get(&target_did) } - && let Some(&(ref target_fqp, target_type)) = - cache.paths.get(&target_did).or_else(get_extern) + && let get_extern = { || cache.external_paths.get(&target_did).map(|(fqp, shortty)| (fqp, *shortty)) } + && let Some((target_fqp, target_type)) = + cache.paths.get(&target_did).map(|info| (&info.parts, info.ty)).or_else(get_extern) && target_type.is_adt() // primitives cannot be inlined && let Some(self_did) = it.item_id.as_def_id() - && let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) } + && let get_local = { || cache.paths.get(&self_did).map(|info| &info.parts) } && let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) { let mut js_src_path: UrlPartsBuilder = diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 4c93e632ab467..b459ead9481cb 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -1279,7 +1279,7 @@ pub(crate) fn build_index( for &OrphanImplItem { impl_id, parent, trait_parent, ref item, ref impl_generics } in &cache.orphan_impl_items { - if let Some((fqp, _)) = cache.paths.get(&parent) { + if let Some(path_info) = cache.paths.get(&parent) { let info = IndexItemInfo::new( tcx, cache, @@ -1291,7 +1291,7 @@ pub(crate) fn build_index( search_index.push(IndexItem { defid: item.item_id.as_def_id(), name: item.name.unwrap(), - module_path: fqp[..fqp.len() - 1].to_vec(), + module_path: path_info.parts[..path_info.parts.len() - 1].to_vec(), parent: Some(parent), parent_idx: None, trait_parent, @@ -1418,8 +1418,15 @@ pub(crate) fn build_index( cache .paths .get(&defid) - .or_else(|| check_external.then(|| cache.external_paths.get(&defid)).flatten()) - .map(|&(ref fqp, ty)| { + .map(|info| (&info.parts, info.ty)) + .or_else(|| { + check_external + .then(|| { + cache.external_paths.get(&defid).map(|(parts, ty)| (parts, *ty)) + }) + .flatten() + }) + .map(|(fqp, ty)| { let pathid = serialized_index.names.len(); match serialized_index.crate_paths_index.entry((ty, fqp.clone())) { Entry::Occupied(entry) => *entry.get(), @@ -1661,8 +1668,10 @@ pub(crate) fn build_index( used_in_function_signature, )), RenderTypeId::DefId(defid) => { - if let Some(&(ref fqp, item_type)) = - paths.get(&defid).or_else(|| external_paths.get(&defid)) + if let Some((fqp, item_type)) = paths + .get(&defid) + .map(|info| (&info.parts, info.ty)) + .or_else(|| external_paths.get(&defid).map(|(parts, ty)| (parts, *ty))) { if tcx.lang_items().fn_mut_trait() == Some(defid) || tcx.lang_items().fn_once_trait() == Some(defid) @@ -1974,8 +1983,11 @@ pub(crate) fn get_function_type_for_search( let impl_or_trait_generics = impl_generics.or_else(|| { if let Some(def_id) = parent && let Some(trait_) = cache.traits.get(&def_id) - && let Some((path, _)) = - cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id)) + && let Some((path, _)) = cache + .paths + .get(&def_id) + .map(|info| (&info.parts, info.ty)) + .or_else(|| cache.external_paths.get(&def_id).map(|(parts, ty)| (parts, *ty))) { let path = clean::Path { res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id), diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index ab72edacae296..5d0dd8ab899f5 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -833,12 +833,17 @@ impl TraitAliasPart { // FIXME: this is a vague explanation for why this can't be a `get`, in // theory it should be... let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) { - Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) { + Some(p) => match cache + .paths + .get(&did) + .map(|info| (&info.parts, info.ty)) + .or_else(|| cache.external_paths.get(&did).map(|(parts, ty)| (parts, *ty))) + { Some((_, t)) => (p, t), None => continue, }, None => match cache.external_paths.get(&did) { - Some((p, t)) => (p, t), + Some((p, t)) => (p, *t), None => continue, }, }; @@ -986,8 +991,10 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { return; } let Some(target_did) = t.type_.def_id(cache) else { return }; - let get_extern = { || cache.external_paths.get(&target_did) }; - let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern) + let get_extern = + { || cache.external_paths.get(&target_did).map(|(parts, ty)| (parts, *ty)) }; + let Some((target_fqp, target_type)) = + cache.paths.get(&target_did).map(|info| (&info.parts, info.ty)).or_else(get_extern) else { return; }; @@ -1003,7 +1010,7 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { .collect(); AliasedType { target_fqp: &target_fqp[..], target_type, impl_ } }); - let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) }; + let get_local = { || cache.paths.get(&self_did).map(|info| &info.parts) }; let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { return; }; diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index f161b31f94dcb..bdd2b7d416d80 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -113,8 +113,9 @@ impl<'tcx> JsonRenderer<'tcx> { .cache .paths .iter() - .chain(&self.cache.external_paths) - .map(|(&k, &(ref path, kind))| { + .map(|(k, info)| (k, (&info.parts, info.ty))) + .chain(self.cache.external_paths.iter().map(|(k, (parts, ty))| (k, (parts, *ty)))) + .map(|(&k, (path, kind))| { ( self.id_from_item_default(k.into()), types::ItemSummary { @@ -195,8 +196,9 @@ impl<'tcx> JsonRenderer<'tcx> { self.cache .paths .get(&item_id) - .or_else(|| self.cache.external_paths.get(&item_id)) - .map(|(path, _)| path.iter().map(|name| name.to_string()).collect()) + .map(|info| &info.parts) + .or_else(|| self.cache.external_paths.get(&item_id).map(|(parts, _)| parts)) + .map(|path| path.iter().map(|name| name.to_string()).collect()) } } From 3ddd7e4b0a580e028099ed8aaf6b2a0466d6a44b Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Fri, 11 Sep 2026 14:50:21 -0400 Subject: [PATCH 09/76] adjust docs to latest offload usage --- src/doc/rustc-dev-guide/src/offload/usage.md | 29 ++++---------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/offload/usage.md b/src/doc/rustc-dev-guide/src/offload/usage.md index 77b8935fab837..56a29d723b2d9 100644 --- a/src/doc/rustc-dev-guide/src/offload/usage.md +++ b/src/doc/rustc-dev-guide/src/offload/usage.md @@ -89,38 +89,21 @@ Now we generate the device (GPU) code, passing the manifest: ``` RUSTFLAGS="-Ctarget-cpu=gfx90a --emit=llvm-bc,llvm-ir -Zoffload=Device=/absolute/path/to/offload.manifest -Csave-temps -Zunstable-options" cargo +offload build -Zunstable-options -r -v --target amdgcn-amd-amdhsa -Zbuild-std=core ``` -You might afterwards need to copy your target/release/deps/.bc to lib.bc for now, before the next step. -Now we generate the host (CPU) code. +Next we generate the host (CPU) code. ``` -RUSTFLAGS="--emit=llvm-bc,llvm-ir -Csave-temps -Zoffload=Host=/p/lustre1/drehwald1/prog/offload/r/target/amdgcn-amd-amdhsa/release/deps/device.bin -Zunstable-options" cargo +offload build -r -``` -This call also does a lot of work and generates multiple intermediate files for LLVM offload. -While we integrated most offload steps into rustc by now, one binary invocation still remains for now: - -``` -"clang-linker-wrapper" "--should-extract=gfx90a" "--device-compiler=amdgcn-amd-amdhsa=-g" "--device-compiler=amdgcn-amd-amdhsa=-save-temps=cwd" "--device-linker=amdgcn-amd-amdhsa=-lompdevice" "--host-triple=x86_64-unknown-linux-gnu" "--save-temps" "--linker-path=/ABSOlUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/lld/bin/ld.lld" "--hash-style=gnu" "--eh-frame-hdr" "-m" "elf_x86_64" "-pie" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-o" "main" "/lib/../lib64/Scrt1.o" "/lib/../lib64/crti.o" "/ABSOLUTE_PATH_TO/crtbeginS.o" "-L/ABSOLUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/llvm/bin/../lib/x86_64-unknown-linux-gnu" "-L/ABSOLUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/llvm/lib/clang/21/lib/x86_64-unknown-linux-gnu" "-L/lib/../lib64" "-L/usr/lib64" "-L/lib" "-L/usr/lib" "target//release/host.o" "-lstdc++" "-lm" "-lomp" "-lomptarget" "-L/ABSOLUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/llvm/lib" "-lgcc_s" "-lgcc" "-lpthread" "-lc" "-lgcc_s" "-lgcc" "/ABSOLUTE_PATH_TO/crtendS.o" "/lib/../lib64/crtn.o" -``` - -You can try to find the paths to those files on your system. -However, I recommend to not fix the paths, but rather just re-generate them by copying a bare-mode OpenMP example and compiling it with your clang. -By adding `-###` to your clang invocation, you can see the invidual steps. -It will show multiple steps, just look for the clang-linker-wrapper example. -Make sure to still include the path to the `host.o` file, and not whatever tmp file you got when compiling your c++ example with the following call. -``` -myclang++ -fuse-ld=lld -O3 -fopenmp -fopenmp-offload-mandatory --offload-arch=gfx90a omp_bare.cpp -o main -### +RUSTFLAGS="--emit=llvm-bc,llvm-ir -Csave-temps -Zoffload=Host=$PWD/target/amdgcn-amd-amdhsa/release/deps/device.bin -Zunstable-options" cargo +offload build -r ``` In the final step, you can now run your binary ``` -./main +LD_LIBRARY_PATH=$(rustc +nightly --print sysroot)/lib ./target/x86_64-unknown-linux-gnu/release/binary-name all checks passed! ``` -To receive more information about the memory transfer, you can enable info printing with -``` -LIBOMPTARGET_INFO=-1 ./main -``` +These three steps will soon be wrapped into a single command, once we had more time to test all steps. + +To receive more information about the memory transfer, you can enable info printing by adding `LIBOMPTARGET_INFO=-1` ahead of your binary call. [^list]: https://rocm.docs.amd.com/en/latest/reference/gpu-arch-specs.html or https://developer.nvidia.com/cuda/gpus. Alternatively, check `rustc --print target-cpus`. From 10e1f7e23a485d168da2b80d869bfb1cc6bbdbad Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Fri, 11 Sep 2026 15:01:04 -0400 Subject: [PATCH 10/76] adjust docs to latest offload install --- src/doc/rustc-dev-guide/src/offload/installation.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index ab8e7984d5b4a..3641f20ac4f1a 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -3,9 +3,16 @@ `std::offload` is partly available in nightly builds for users. For now, everyone however still needs to build rustc from source to use all features of it. +## Rustup installation. +If you are on `x86_64` Linux, you can install the nightly toolchain with: +```console +rustup +nightly component add offload +``` + + ## Build instructions -First you need to clone and configure the Rust repository: +Otherwise you need to clone and configure the Rust repository: ```console git clone git@github.com:rust-lang/rust cd rust From 1629bcdb5da8963de38e6c8eef97fece99b480e6 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Sun, 13 Sep 2026 18:30:48 +0200 Subject: [PATCH 11/76] Add useful APIs to `Unique(Arc|Rc)` --- library/alloc/src/rc.rs | 85 +++++++++++++++++++++++++++++++------- library/alloc/src/sync.rs | 86 ++++++++++++++++++++++++++++++++------- 2 files changed, 141 insertions(+), 30 deletions(-) diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index b2afe0b464eb6..eebbf7a5baed5 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -4326,6 +4326,13 @@ impl UniqueRc { pub fn new(value: T) -> Self { Self::new_in(value, Global) } + + /// Like [`new`](Self::new), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn try_new(value: T) -> Result { + Self::try_new_in(value, Global) + } } impl UniqueRc { @@ -4337,8 +4344,8 @@ impl UniqueRc { /// point to the new [`Rc`]. #[cfg(not(no_global_oom_handling))] #[unstable(feature = "unique_rc_arc", issue = "112566")] - #[must_use] // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] pub fn new_in(value: T, alloc: A) -> Self { let (ptr, alloc) = Box::into_unique(Box::new_in( RcInner { @@ -4353,8 +4360,29 @@ impl UniqueRc { Self { ptr: ptr.into(), _marker: PhantomData, _marker2: PhantomData, alloc } } - #[cfg(not(no_global_oom_handling))] - fn unwrap_with_allocator(this: Self) -> (T, A) { + /// Like [`new_in`](Self::new_in), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + pub fn try_new_in(value: T, alloc: A) -> Result { + let (ptr, alloc) = Box::into_non_null_with_allocator(Box::try_new_in( + RcInner { + strong: Cell::new(0), + // keep one weak reference so if all the weak pointers that are created are dropped + // the UniqueRc still stays valid. + weak: Cell::new(1), + value, + }, + alloc, + )?); + Ok(Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc }) + } + + /// Consumes the `UniqueRc`, returning its wrapped value and allocator. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] + pub fn unwrap_with_allocator(this: Self) -> (T, A) { let inner_ptr = this.ptr; let (data_ptr, alloc) = Self::into_raw_with_allocator(this); @@ -4368,6 +4396,13 @@ impl UniqueRc { (val, alloc) } + /// Consumes the `UniqueRc`, returning its wrapped value. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn unwrap(this: Self) -> T { + Self::unwrap_with_allocator(this).0 + } + /// Maps the value in a `UniqueRc`, reusing the allocation if possible. /// /// `f` is called on a reference to the value in the `UniqueRc`, and the result is returned, @@ -4399,11 +4434,10 @@ impl UniqueRc { unsafe { let (ptr, alloc) = UniqueRc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = + let allocation = UniqueRc::from_raw_with_allocator(ptr.cast::>(), alloc); - allocation.write(f(value)); - allocation.assume_init() + UniqueRc::write(allocation, f(value)) } } else { let (val, alloc) = UniqueRc::unwrap_with_allocator(this); @@ -4450,13 +4484,12 @@ impl UniqueRc { unsafe { let (ptr, alloc) = UniqueRc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = UniqueRc::from_raw_with_allocator( + let allocation = UniqueRc::from_raw_with_allocator( ptr.cast::>(), alloc, ); - allocation.write(f(value)?); - try { allocation.assume_init() } + try { UniqueRc::write(allocation, f(value)?) } } } else { let (val, alloc) = UniqueRc::unwrap_with_allocator(this); @@ -4484,7 +4517,6 @@ impl UniqueRc { } } - #[cfg(not(no_global_oom_handling))] fn into_raw_with_allocator(this: Self) -> (*const T, A) { let this = ManuallyDrop::new(this); // SAFETY: The copy of the allocator stored in `this` is forgotten @@ -4526,7 +4558,6 @@ impl UniqueRc { unsafe { self.ptr.as_ref() } } - #[cfg(not(no_global_oom_handling))] fn as_ptr(this: &Self) -> *const T { let ptr: *mut RcInner = NonNull::as_ptr(this.ptr); @@ -4537,7 +4568,6 @@ impl UniqueRc { } #[inline] - #[cfg(not(no_global_oom_handling))] fn into_inner_with_allocator(this: Self) -> (NonNull>, A) { let this = mem::ManuallyDrop::new(this); // SAFETY: Pointer is valid for reads. @@ -4545,7 +4575,6 @@ impl UniqueRc { } #[inline] - #[cfg(not(no_global_oom_handling))] unsafe fn from_inner_in(ptr: NonNull>, alloc: A) -> Self { Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc } } @@ -4567,9 +4596,35 @@ impl UniqueRc { } } -#[cfg(not(no_global_oom_handling))] impl UniqueRc, A> { - unsafe fn assume_init(self) -> UniqueRc { + /// Writes the value and converts to `UniqueRc`. + /// + /// This method converts similarly to [`assume_init`](Self::assume_init) but + /// writes `value` into it before conversion, thus guaranteeing safety. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn write(mut this: Self, value: T) -> UniqueRc { + // SAFETY: Writing initialises the wrapped value. + unsafe { + this.write(value); + this.assume_init() + } + } + + /// Converts to `UniqueRc`. + /// + /// # Safety + /// + /// As with [`MaybeUninit::assume_init`], + /// it is up to the caller to guarantee that the value + /// really is in an initialized state. + /// Calling this when the content is not yet fully initialized + /// causes immediate undefined behavior. + /// + /// [`MaybeUninit::assume_init`]: mem::MaybeUninit::assume_init + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub unsafe fn assume_init(self) -> UniqueRc { let (ptr, alloc) = UniqueRc::into_inner_with_allocator(self); // SAFETY: Upheld by caller. unsafe { UniqueRc::from_inner_in(ptr.cast(), alloc) } diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 09a371f94bbb9..343ab05f68bc6 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -4803,6 +4803,13 @@ impl UniqueArc { pub fn new(value: T) -> Self { Self::new_in(value, Global) } + + /// Like [`new`](Self::new), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn try_new(value: T) -> Result { + Self::try_new_in(value, Global) + } } impl UniqueArc { @@ -4814,8 +4821,8 @@ impl UniqueArc { /// point to the new [`Arc`]. #[cfg(not(no_global_oom_handling))] #[unstable(feature = "unique_rc_arc", issue = "112566")] - #[must_use] // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] pub fn new_in(data: T, alloc: A) -> Self { let (ptr, alloc) = Box::into_unique(Box::new_in( ArcInner { @@ -4830,8 +4837,29 @@ impl UniqueArc { Self { ptr: ptr.into(), _marker: PhantomData, _marker2: PhantomData, alloc } } - #[cfg(not(no_global_oom_handling))] - fn unwrap_with_allocator(this: Self) -> (T, A) { + /// Like [`new_in`](Self::new_in), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + pub fn try_new_in(data: T, alloc: A) -> Result { + let (ptr, alloc) = Box::into_non_null_with_allocator(Box::try_new_in( + ArcInner { + strong: atomic::AtomicUsize::new(0), + // keep one weak reference so if all the weak pointers that are created are dropped + // the UniqueArc still stays valid. + weak: atomic::AtomicUsize::new(1), + data, + }, + alloc, + )?); + Ok(Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc }) + } + + /// Consumes the `UniqueArc`, returning its wrapped value and allocator. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] + pub fn unwrap_with_allocator(this: Self) -> (T, A) { let inner_ptr = this.ptr; let (data_ptr, alloc) = Self::into_raw_with_allocator(this); @@ -4839,11 +4867,19 @@ impl UniqueArc { // We do not use the data inside ever again. let val = unsafe { data_ptr.read() }; + // Drop the strong-weak ref drop(Weak { ptr: inner_ptr, alloc: &alloc }); (val, alloc) } + /// Consumes the `UniqueArc`, returning its wrapped value. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn unwrap(this: Self) -> T { + Self::unwrap_with_allocator(this).0 + } + /// Maps the value in a `UniqueArc`, reusing the allocation if possible. /// /// `f` is called on a reference to the value in the `UniqueArc`, and the result is returned, @@ -4875,11 +4911,10 @@ impl UniqueArc { unsafe { let (ptr, alloc) = UniqueArc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = + let allocation = UniqueArc::from_raw_with_allocator(ptr.cast::>(), alloc); - allocation.write(f(value)); - allocation.assume_init() + UniqueArc::write(allocation, f(value)) } } else { let (val, alloc) = UniqueArc::unwrap_with_allocator(this); @@ -4926,13 +4961,12 @@ impl UniqueArc { unsafe { let (ptr, alloc) = UniqueArc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = UniqueArc::from_raw_with_allocator( + let allocation = UniqueArc::from_raw_with_allocator( ptr.cast::>(), alloc, ); - allocation.write(f(value)?); - try { allocation.assume_init() } + try { UniqueArc::write(allocation, f(value)?) } } } else { let (val, alloc) = UniqueArc::unwrap_with_allocator(this); @@ -4960,7 +4994,6 @@ impl UniqueArc { } } - #[cfg(not(no_global_oom_handling))] fn into_raw_with_allocator(this: Self) -> (*const T, A) { let this = ManuallyDrop::new(this); // SAFETY: The copy of the allocator stored in `this` is forgotten @@ -5003,7 +5036,6 @@ impl UniqueArc { unsafe { self.ptr.as_ref() } } - #[cfg(not(no_global_oom_handling))] fn as_ptr(this: &Self) -> *const T { let ptr: *mut ArcInner = NonNull::as_ptr(this.ptr); @@ -5014,7 +5046,6 @@ impl UniqueArc { } #[inline] - #[cfg(not(no_global_oom_handling))] fn into_inner_with_allocator(this: Self) -> (NonNull>, A) { let this = mem::ManuallyDrop::new(this); // SAFETY: Pointer is valid for reads and only read once. @@ -5022,7 +5053,6 @@ impl UniqueArc { } #[inline] - #[cfg(not(no_global_oom_handling))] unsafe fn from_inner_in(ptr: NonNull>, alloc: A) -> Self { Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc } } @@ -5056,9 +5086,35 @@ impl UniqueArc { } } -#[cfg(not(no_global_oom_handling))] impl UniqueArc, A> { - unsafe fn assume_init(self) -> UniqueArc { + /// Writes the value and converts to `UniqueArc`. + /// + /// This method converts similarly to [`assume_init`](Self::assume_init) but + /// writes `value` into it before conversion, thus guaranteeing safety. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn write(mut this: Self, value: T) -> UniqueArc { + // SAFETY: Writing initialises the wrapped value. + unsafe { + this.write(value); + this.assume_init() + } + } + + /// Converts to `UniqueArc`. + /// + /// # Safety + /// + /// As with [`MaybeUninit::assume_init`], + /// it is up to the caller to guarantee that the value + /// really is in an initialized state. + /// Calling this when the content is not yet fully initialized + /// causes immediate undefined behavior. + /// + /// [`MaybeUninit::assume_init`]: mem::MaybeUninit::assume_init + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub unsafe fn assume_init(self) -> UniqueArc { let (ptr, alloc) = UniqueArc::into_inner_with_allocator(self); // SAFETY: Upheld by caller. unsafe { UniqueArc::from_inner_in(ptr.cast(), alloc) } From 7e374726d3525ca7f27138fd5c4e13d42e697d2e Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 14 Sep 2026 04:26:47 +0000 Subject: [PATCH 12/76] Prepare for merging from rust-lang/rust This updates the rust-version file to 4b6d04e706108ccfeafe2547fbe857dfe8972bad. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 18fea436747c7..f7d3ea0b9325b 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba +4b6d04e706108ccfeafe2547fbe857dfe8972bad From a74f1e7d47b9796cc76e0345fb3194b0a740d776 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Mon, 14 Sep 2026 23:32:53 -0400 Subject: [PATCH 13/76] fix generic offload usage --- src/doc/rustc-dev-guide/src/offload/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/offload/usage.md b/src/doc/rustc-dev-guide/src/offload/usage.md index 56a29d723b2d9..9d61a5a87c744 100644 --- a/src/doc/rustc-dev-guide/src/offload/usage.md +++ b/src/doc/rustc-dev-guide/src/offload/usage.md @@ -49,7 +49,7 @@ fn kernel(x: *mut [T; 256], value: T) { fn main() { let mut x = [0.0f64; 256]; core::offload::offload! { - kernel = kernel, + kernel = kernel::, workgroup_dim = [256, 1, 1], args = (&mut x as *mut [f64; 256], 2.5), } From a1864bdeb0c5252093ca7948f83d793ef712f555 Mon Sep 17 00:00:00 2001 From: ltdk Date: Tue, 15 Sep 2026 23:07:58 -0400 Subject: [PATCH 14/76] Ping T-libs-ping instead of T-libs-fcp for backports --- triagebot.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triagebot.toml b/triagebot.toml index fc9c43d2dbcae..67799f0a944ef 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -850,7 +850,7 @@ zulip_stream = 542373 # #t-libs/backports topic = "#{number}: beta-nominated" message_on_add = [ """\ -@*T-libs-fcp* PR #{number} "{title}" has been nominated for beta backport. +@*T-libs-ping* PR #{number} "{title}" has been nominated for beta backport. """, """\ /poll Should #{number} be beta backported? @@ -874,7 +874,7 @@ zulip_stream = 542373 # #t-libs/backports topic = "#{number}: stable-nominated" message_on_add = [ """\ -@*T-libs-fcp* PR #{number} "{title}" has been nominated for stable backport. +@*T-libs-ping* PR #{number} "{title}" has been nominated for stable backport. """, """\ /poll Approve stable backport of #{number}? From f68cc95efd2c639071fecc320771a3eabc579fa9 Mon Sep 17 00:00:00 2001 From: Johann Hemmann Date: Wed, 16 Sep 2026 13:25:03 +0200 Subject: [PATCH 15/76] Fix formatting, and make wording consistent --- src/doc/rustc-dev-guide/src/backend/monomorph.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/backend/monomorph.md b/src/doc/rustc-dev-guide/src/backend/monomorph.md index 670614fe51379..f25211b23e602 100644 --- a/src/doc/rustc-dev-guide/src/backend/monomorph.md +++ b/src/doc/rustc-dev-guide/src/backend/monomorph.md @@ -33,7 +33,7 @@ Take this example: ```rust fn banana() { - peach::(); + peach::(); } fn main() { @@ -67,9 +67,9 @@ or more modules in Crate B. | Crate A function | Behavior | | - | - | -| Non-generic function | Crate A function doesn't appear in any codegen units of Crate B | -| Non-generic `#[inline]` function | Crate A function appears within a single CGU of Crate B, and exists even after post-inlining stage| -| Generic function | Regardless of inlining, all monomorphized (specialized) functions
from Crate A appear within a single codegen unit for Crate B.
The codegen unit exists even after the post inlining stage.| +| Non-generic function | Crate A function doesn't appear in any codegen units of Crate B. | +| Non-generic `#[inline]` function | Crate A function appears within a single CGU of Crate B.
The codegen unit exists even after the post inlining stage. | +| Generic function | Regardless of inlining, all monomorphized (specialized) functions
from Crate A appear within a single codegen unit for Crate B.
The codegen unit exists even after the post inlining stage. | | Generic `#[inline]` function | - same - | For more details about the partitioner read the module level [documentation]. From ba736a028b6386b1c6069125bae336b137e81187 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 12 Sep 2026 01:05:31 +0200 Subject: [PATCH 16/76] Provide the "preferred name" when generating `href` and `title` for intra doc link --- src/librustdoc/clean/types.rs | 6 +++--- src/librustdoc/formats/cache.rs | 22 ++++++++++++++++++++-- src/librustdoc/html/format.rs | 28 +++++++++++++++++++++------- src/librustdoc/html/highlight.rs | 27 +++++++++++++++++---------- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 7a99ce8d39e9c..8ad8cd9997a74 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -600,7 +600,7 @@ impl Item { } pub(crate) fn links(&self, cx: &Context<'_>) -> Vec { - use crate::html::format::{href, link_tooltip}; + use crate::html::format::{href_with_path_check, link_tooltip}; let Some(links) = cx.cache().intra_doc_links.get(&self.item_or_reexport_id()) else { return vec![]; @@ -609,7 +609,7 @@ impl Item { .iter() .filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| { debug!(?id); - if let Ok(HrefInfo { mut url, .. }) = href(*id, cx) { + if let Ok(HrefInfo { mut url, .. }) = href_with_path_check(*id, cx, link_text) { debug!(?url); match fragment { Some(UrlFragment::Item(def_id)) => { @@ -625,7 +625,7 @@ impl Item { Some(RenderedLink { original_text: s.clone(), new_text: link_text.clone(), - tooltip: link_tooltip(*id, fragment, cx).to_string(), + tooltip: link_tooltip(*id, fragment, cx, Some(link_text)).to_string(), href: url, }) } else { diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 3dee84c2f4dc4..c89c3dbd3fbf5 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -19,7 +19,8 @@ use crate::html::render::{IndexItem, IndexItemInfo}; use crate::visit_lib::RustdocEffectiveVisibilities; pub(crate) struct PathInfo { - /// Parts of the path. So in `foo::bar::bib`, it will be `["foo", "bar", "bib"]`. + /// Parts of the fully qualified path. So in `foo::bar::bib`, it will + /// be `["foo", "bar", "bib"]`. pub(crate) parts: Vec, pub(crate) ty: ItemType, /// When a reexport inline an item, we can end up with the same `DefId` with multiple local @@ -35,10 +36,27 @@ pub(crate) struct PathInfo { /// ``` /// /// To ensure that `a1` and `a2` links to `a1` and `a2` which have the same `DefId`, we need - /// to store both paths. + /// to store both `a1` and `a2` paths. + /// + /// The path stored in `parts` is not present in `alternatives`. pub(crate) alternatives: Vec>, } +impl PathInfo { + pub(crate) fn get_preferred_path(&self, preferred_name: Option<&str>) -> &[Symbol] { + if let Some(preferred_name) = preferred_name + && let Some(alternative_path) = self + .alternatives + .iter() + .find(|path| path.last().is_some_and(|last| last.as_str() == preferred_name)) + { + alternative_path + } else { + &self.parts + } + } +} + /// This cache is used to store information about the [`clean::Crate`] being /// rendered in order to provide more useful documentation. This contains /// information like all implementors of a trait, all traits a type implements, diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 07b0f02b7d5b8..da150789b2102 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -543,6 +543,7 @@ pub(crate) fn href_with_root_path( original_did: DefId, cx: &Context<'_>, root_path: Option<&str>, + preferred_name: Option<&str>, ) -> Result { let tcx = cx.tcx(); let def_kind = tcx.def_kind(original_did); @@ -553,7 +554,9 @@ pub(crate) fn href_with_root_path( } // If this a constructor, we get the parent (either a struct or a variant) and then // generate the link for this item. - DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path), + DefKind::Ctor(..) => { + return href_with_root_path(tcx.parent(original_did), cx, root_path, preferred_name); + } DefKind::ExternCrate => { // Link to the crate itself, not the `extern crate` item. if let Some(local_did) = original_did.as_local() { @@ -587,7 +590,7 @@ pub(crate) fn href_with_root_path( let (fqp, shortty, url_parts, is_absolute) = match cache.paths.get(&did) { Some(info) => ( - &info.parts, + info.get_preferred_path(preferred_name), info.ty, { let module_fqp = to_module_fqp(info.ty, info.parts.as_slice()); @@ -604,7 +607,7 @@ pub(crate) fn href_with_root_path( if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) { let module_fqp = to_module_fqp(shortty, fqp); let (parts, is_absolute) = url_parts(cache, did, module_fqp, relative_to)?; - (fqp, shortty, parts, is_absolute) + (fqp.as_slice(), shortty, parts, is_absolute) } else if matches!(def_kind, DefKind::Macro(_)) { return generate_macro_def_id_path(did, cx, root_path); } else if did.is_local() { @@ -617,12 +620,20 @@ pub(crate) fn href_with_root_path( Ok(HrefInfo { url: make_href(root_path, shortty, url_parts, fqp, is_absolute), kind: shortty, - rust_path: fqp.clone(), + rust_path: fqp.to_vec(), }) } pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result { - href_with_root_path(did, cx, None) + href_with_root_path(did, cx, None, None) +} + +pub(crate) fn href_with_path_check( + did: DefId, + cx: &Context<'_>, + text: &str, +) -> Result { + href_with_root_path(did, cx, None, Some(text)) } /// Both paths should only be modules. @@ -660,14 +671,17 @@ pub(crate) fn link_tooltip( did: DefId, fragment: &Option, cx: &Context<'_>, + preferred_name: Option<&str>, ) -> impl fmt::Display { fmt::from_fn(move |f| { let cache = cx.cache(); let Some((fqp, shortty)) = cache .paths .get(&did) - .map(|info| (&info.parts, info.ty)) - .or_else(|| cache.external_paths.get(&did).map(|(fqp, shortty)| (fqp, *shortty))) + .map(|info| (info.get_preferred_path(preferred_name), info.ty)) + .or_else(|| { + cache.external_paths.get(&did).map(|(fqp, shortty)| (fqp.as_slice(), *shortty)) + }) else { return Ok(()); }; diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 89d50680c3a3b..9c73e3b4ad687 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -1406,23 +1406,30 @@ fn generate_link_to_def( LinkFromSrc::Local(span) => { context.href_from_span_relative(*span, &href_context.current_href) } - LinkFromSrc::External(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|HrefInfo { url, .. }| url) - } + LinkFromSrc::External(def_id) => format::href_with_root_path( + *def_id, + context, + Some(href_context.root_path), + None, + ) + .ok() + .map(|HrefInfo { url, .. }| url), LinkFromSrc::Primitive(prim) => format::href_with_root_path( PrimitiveType::primitive_locations(context.tcx())[prim], context, Some(href_context.root_path), + None, + ) + .ok() + .map(|HrefInfo { url, .. }| url), + LinkFromSrc::Doc(def_id) => format::href_with_root_path( + *def_id, + context, + Some(href_context.root_path), + None, ) .ok() .map(|HrefInfo { url, .. }| url), - LinkFromSrc::Doc(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|HrefInfo { url, .. }| url) - } } }) { From cd6287f229efa4a9637621a0df5839d580794526 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 12 Sep 2026 01:17:06 +0200 Subject: [PATCH 17/76] Add regression test for inlined same item with different names --- .../inline-same-item-with-different-names.rs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs diff --git a/tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs b/tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs new file mode 100644 index 0000000000000..c7801da5c98c2 --- /dev/null +++ b/tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs @@ -0,0 +1,43 @@ +// This test ensures that when a same item is inlined with different names, the intra +// doc links generate the correct href/title. +// Regression test for . + +#![crate_name = "foo"] + +// We check that the macros and structs are correctly generated. +//@ has 'foo/macro.d1.html' +//@ has 'foo/macro.d2.html' +//@ has 'foo/macro.d3.html' +//@ has 'foo/struct.a1.html' +//@ has 'foo/struct.a2.html' +//@ has 'foo/struct.a3.html' + +//@ has 'foo/index.html' + +//@ has - '//dd/a[@href="macro.d1.html"]' 'd1' +//@ has - '//dd/a[@title="macro foo::d1"]' 'd1' +//@ has - '//dd/a[@href="macro.d2.html"]' 'd2' +//@ has - '//dd/a[@title="macro foo::d2"]' 'd2' +//@ has - '//dd/a[@href="macro.d3.html"]' 'd3' +//@ has - '//dd/a[@title="macro foo::d3"]' 'd3' + +/// Link to [`d3`]. +pub use std::debug_assert as d1; +/// Link to [`d1`]. +pub use std::debug_assert as d2; +/// Link to [`d2`]. +pub use std::debug_assert as d3; + +//@ has - '//dd/a[@href="struct.a1.html"]' 'a1' +//@ has - '//dd/a[@title="struct foo::a1"]' 'a1' +//@ has - '//dd/a[@href="struct.a2.html"]' 'a2' +//@ has - '//dd/a[@title="struct foo::a2"]' 'a2' +//@ has - '//dd/a[@href="struct.a3.html"]' 'a3' +//@ has - '//dd/a[@title="struct foo::a3"]' 'a3' + +/// Link to [`a3`]. +pub use std::ffi::os_str::OsString as a1; +/// Link to [`a1`]. +pub use std::ffi::os_str::OsString as a2; +/// Link to [`a2`]. +pub use std::ffi::os_str::OsString as a3; From 005aa4525614598ccedbc01768ccfb59c8fb833e Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 16 Sep 2026 22:16:50 -0400 Subject: [PATCH 18/76] Mini optimization in `restrict_precision_for_drop_types` Avoid calling `type_is_copy_modulo_regions` if not necessary. --- compiler/rustc_hir_typeck/src/upvar.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 1497bfeb0f774..875517ee4c87e 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -2318,9 +2318,9 @@ fn restrict_precision_for_drop_types<'a, 'tcx>( mut place: Place<'tcx>, mut curr_mode: ty::UpvarCapture, ) -> (Place<'tcx>, ty::UpvarCapture) { - let is_copy_type = fcx.infcx.type_is_copy_modulo_regions(fcx.param_env, place.ty()); - - if let (false, UpvarCapture::ByValue) = (is_copy_type, curr_mode) { + if curr_mode == UpvarCapture::ByValue + && !fcx.infcx.type_is_copy_modulo_regions(fcx.param_env, place.ty()) + { for i in 0..place.projections.len() { match place.ty_before_projection(i).kind() { ty::Adt(def, _) if def.destructor(fcx.tcx).is_some() => { From 085678ad35d740494b98aa9774bae60ce824550b Mon Sep 17 00:00:00 2001 From: Flakebi Date: Tue, 1 Sep 2026 09:40:37 +0200 Subject: [PATCH 19/76] Add address_space and byref to abi PassMode::Indirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both will be used by the amdgpu target to implement the `gpu-kernel` ABI. `address_space` specifies the address space of an indirect argument. `AmdgpuKernelArg` translates to LLVM’s byref, which is similar to on_stack/byval, however, there is no extra copy made, the pointer may not point to the stack but can point to some other address space, and the passed argument should not be modified. byval and byref are mutually exclusive, so change on_stack to an enum with the new states, Pointer (none), OnStack and AmdgpuKernelArg. --- compiler/rustc_abi/src/layout/ty.rs | 4 +- .../src/abi/pass_mode.rs | 24 ++-- .../src/abi/returning.rs | 17 +-- compiler/rustc_codegen_gcc/src/abi.rs | 33 ++++- compiler/rustc_codegen_llvm/src/abi.rs | 111 +++++++++++++--- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + compiler/rustc_codegen_llvm/src/llvm/mod.rs | 4 + compiler/rustc_codegen_ssa/src/mir/block.rs | 48 ++++--- compiler/rustc_codegen_ssa/src/mir/mod.rs | 16 ++- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 5 + .../src/deduce_param_attrs.rs | 2 +- compiler/rustc_public/src/abi.rs | 18 ++- .../src/unstable/convert/stable/abi.rs | 35 +++-- compiler/rustc_target/src/callconv/mod.rs | 125 ++++++++++++++---- compiler/rustc_target/src/callconv/x86.rs | 5 +- compiler/rustc_target/src/callconv/xtensa.rs | 6 +- compiler/rustc_ty_utils/src/abi.rs | 14 +- tests/assembly-llvm/tail-call-indirect.rs | 6 +- tests/ui-fulldeps/rustc_public/check_abi.rs | 8 +- .../rustc_public/check_abi_cast.rs | 4 +- tests/ui/abi/c-zst.powerpc-linux.stderr | 3 +- tests/ui/abi/c-zst.s390x-linux.stderr | 3 +- tests/ui/abi/c-zst.sparc64-linux.stderr | 3 +- .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 3 +- tests/ui/abi/debug.generic.stderr | 6 +- tests/ui/abi/debug.loongarch64.stderr | 6 +- tests/ui/abi/debug.riscv64.stderr | 6 +- tests/ui/abi/pass-indirectly-attr.rs | 2 +- tests/ui/abi/pass-indirectly-attr.stderr | 3 +- .../pass-by-value-abi.aarch64.stderr | 3 +- tests/ui/c-variadic/pass-by-value-abi.rs | 8 +- .../pass-by-value-abi.x86_64.stderr | 9 +- tests/ui/explicit-tail-calls/indirect.rs | 10 +- 33 files changed, 402 insertions(+), 149 deletions(-) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 3b3a58697b205..54dede083f595 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -253,8 +253,8 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } /// If this method returns `true`, then this type should always have a `PassMode` of - /// `Indirect { on_stack: false, .. }` when being used as the argument type of a function with a - /// non-Rustic ABI (this is true for structs annotated with the + /// `Indirect { mode: IndirectMode::Pointer, .. }` when being used as the argument type of a + /// function with a non-Rustic ABI (this is true for structs annotated with the /// `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute). /// /// This is used to replicate some of the behaviour of C array-to-pointer decay; however unlike diff --git a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs index 1c552ca1a9c32..48ffc43c5cfa1 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs @@ -3,7 +3,7 @@ use cranelift_codegen::ir::ArgumentPurpose; use rustc_abi::{Reg, RegKind}; use rustc_target::callconv::{ - ArgAbi, ArgAttributes, ArgExtension as RustcArgExtension, CastTarget, PassMode, + ArgAbi, ArgAttributes, ArgExtension as RustcArgExtension, CastTarget, IndirectMode, PassMode, }; use smallvec::{SmallVec, smallvec}; @@ -126,8 +126,12 @@ impl<'tcx> ArgAbiExt<'tcx> for ArgAbi<'tcx, Ty<'tcx>> { assert_eq!(pad_i32_count, 0, "padding support not yet implemented"); cast_target_to_abi_params(cast).into_iter().map(|(_, param)| param).collect() } - PassMode::Indirect { attrs, meta_attrs: None, on_stack } => { - if on_stack { + PassMode::Indirect { attrs, meta_attrs: None, address_space: _, mode } => { + assert!( + mode != IndirectMode::AmdgpuKernelArg, + "unsupported amdgpu kernel argument" + ); + if mode == IndirectMode::OnStack { // Abi requires aligning struct size to pointer size let size = self.layout.size.align_to(tcx.data_layout.pointer_align().abi); let size = u32::try_from(size.bytes()).unwrap(); @@ -139,8 +143,8 @@ impl<'tcx> ArgAbiExt<'tcx> for ArgAbi<'tcx, Ty<'tcx>> { smallvec![apply_attrs_to_abi_param(AbiParam::new(pointer_ty(tcx)), attrs)] } } - PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack } => { - assert!(!on_stack); + PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), address_space: _, mode } => { + assert!(mode == IndirectMode::Pointer); smallvec![ apply_attrs_to_abi_param(AbiParam::new(pointer_ty(tcx)), attrs), apply_attrs_to_abi_param(AbiParam::new(pointer_ty(tcx)), meta_attrs), @@ -184,8 +188,8 @@ impl<'tcx> ArgAbiExt<'tcx> for ArgAbi<'tcx, Ty<'tcx>> { None, cast_target_to_abi_params(cast).into_iter().map(|(_, param)| param).collect(), ), - PassMode::Indirect { attrs, meta_attrs: None, on_stack } => { - assert!(!on_stack); + PassMode::Indirect { attrs, meta_attrs: None, address_space: _, mode } => { + assert!(mode == IndirectMode::Pointer); ( Some(apply_attrs_to_abi_param( AbiParam::special(pointer_ty(tcx), ArgumentPurpose::StructReturn), @@ -194,7 +198,7 @@ impl<'tcx> ArgAbiExt<'tcx> for ArgAbi<'tcx, Ty<'tcx>> { vec![], ) } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { unreachable!("unsized return value") } } @@ -324,7 +328,7 @@ pub(super) fn cvalue_for_param<'tcx>( PassMode::Cast { ref cast, .. } => { from_casted_value(fx, &block_params, arg_abi.layout, cast) } - PassMode::Indirect { attrs, meta_attrs: None, on_stack: _ } => { + PassMode::Indirect { attrs, meta_attrs: None, address_space: _, mode: _ } => { assert_eq!(block_params.len(), 1, "{:?}", block_params); if let Some(pointee_align) = attrs.pointee_align && pointee_align < arg_abi.layout.align.abi @@ -342,7 +346,7 @@ pub(super) fn cvalue_for_param<'tcx>( CValue::by_ref(Pointer::new(block_params[0]), arg_abi.layout) } } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { assert_eq!(block_params.len(), 2, "{:?}", block_params); CValue::by_ref_unsized(Pointer::new(block_params[0]), block_params[1], arg_abi.layout) } diff --git a/compiler/rustc_codegen_cranelift/src/abi/returning.rs b/compiler/rustc_codegen_cranelift/src/abi/returning.rs index 36087f96dd776..7f4ee9435b506 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/returning.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/returning.rs @@ -17,12 +17,12 @@ pub(super) fn codegen_return_param<'tcx>( let is_ssa = ssa_analyzed[RETURN_PLACE].is_ssa(fx, fx.fn_abi.ret.layout.ty); (super::make_local_place(fx, RETURN_PLACE, fx.fn_abi.ret.layout, is_ssa), smallvec![]) } - PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } => { let ret_param = block_params_iter.next().unwrap(); assert_eq!(fx.bcx.func.dfg.value_type(ret_param), fx.pointer_type); (CPlace::for_ptr(Pointer::new(ret_param), fx.fn_abi.ret.layout), smallvec![ret_param]) } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { unreachable!("unsized return value") } }; @@ -50,7 +50,7 @@ pub(super) fn codegen_with_call_return_arg<'tcx>( ) { let (ret_temp_place, return_ptr) = match ret_arg_abi.mode { PassMode::Ignore => (None, None), - PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } => { if let Some(ret_ptr) = ret_place.try_to_ptr() { // This is an optimization to prevent unnecessary copies of the return value when // the return place is already a memory place as opposed to a register. @@ -61,7 +61,7 @@ pub(super) fn codegen_with_call_return_arg<'tcx>( (Some(place), Some(place.to_ptr().get_addr(fx))) } } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { unreachable!("unsized return value") } PassMode::Direct(_) | PassMode::Pair(_, _) | PassMode::Cast { .. } => (None, None), @@ -86,14 +86,14 @@ pub(super) fn codegen_with_call_return_arg<'tcx>( super::pass_mode::from_casted_value(fx, &results, ret_place.layout(), cast); ret_place.write_cvalue(fx, result); } - PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } => { if let Some(ret_temp_place) = ret_temp_place { // If ret_temp_place is None, it is not necessary to copy the return value. let ret_temp_value = ret_temp_place.to_cvalue(fx); ret_place.write_cvalue(fx, ret_temp_value); } } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { unreachable!("unsized return value") } } @@ -102,10 +102,11 @@ pub(super) fn codegen_with_call_return_arg<'tcx>( /// Codegen a return instruction with the right return value(s) if any. pub(crate) fn codegen_return(fx: &mut FunctionCx<'_, '_, '_>) { match fx.fn_abi.ret.mode { - PassMode::Ignore | PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => { + PassMode::Ignore + | PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } => { fx.bcx.ins().return_(&[]); } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { unreachable!("unsized return value") } PassMode::Direct(_) => { diff --git a/compiler/rustc_codegen_gcc/src/abi.rs b/compiler/rustc_codegen_gcc/src/abi.rs index 6a05f1cbbeef1..b5834ca57ebe1 100644 --- a/compiler/rustc_codegen_gcc/src/abi.rs +++ b/compiler/rustc_codegen_gcc/src/abi.rs @@ -11,7 +11,7 @@ use rustc_middle::ty::layout::LayoutOf; #[cfg(feature = "master")] use rustc_session::{Session, config}; use rustc_span::bug; -use rustc_target::callconv::{ArgAttributes, CastTarget, FnAbi, PassMode}; +use rustc_target::callconv::{ArgAttributes, CastTarget, FnAbi, IndirectMode, PassMode}; #[cfg(feature = "master")] use rustc_target::spec::Arch; @@ -178,19 +178,42 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { let ty = cast.gcc_type(cx); apply_attrs(ty, &cast.attrs, argument_tys.len()) } - PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: true } => { + PassMode::Indirect { + attrs: _, + meta_attrs: None, + address_space: _, + mode: IndirectMode::OnStack, + } => { // This is a "byval" argument, so we don't apply the `restrict` attribute on it. on_stack_param_indices.insert(argument_tys.len()); arg.layout.gcc_type(cx) } + PassMode::Indirect { + attrs: _, + meta_attrs: None, + address_space: _, + mode: IndirectMode::AmdgpuKernelArg, + } => { + unimplemented!("unsupported amdgpu kernel argument") + } PassMode::Direct(attrs) => { apply_attrs(arg.layout.immediate_gcc_type(cx), &attrs, argument_tys.len()) } - PassMode::Indirect { attrs, meta_attrs: None, on_stack: false } => { + PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::Pointer, + } => { apply_attrs(cx.type_ptr_to(arg.layout.gcc_type(cx)), &attrs, argument_tys.len()) } - PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack } => { - assert!(!on_stack); + PassMode::Indirect { + attrs, + meta_attrs: Some(meta_attrs), + address_space: _, + mode, + } => { + assert!(mode == IndirectMode::Pointer); // Construct the type of a (wide) pointer to `ty`, and pass its two fields. // Any two ABI-compatible unsized types have the same metadata type and // moreover the same metadata value leads to the same dynamic size and diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index a45138849e4e0..703986fdab3fa 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::layout::LayoutOf; use rustc_session::{Session, config}; use rustc_span::bug; use rustc_target::callconv::{ - ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, CastTarget, FnAbi, PassMode, + ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, CastTarget, FnAbi, IndirectMode, PassMode, }; use rustc_target::spec::{Arch, SanitizerSet}; use smallvec::SmallVec; @@ -242,12 +242,12 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> { match &self.mode { PassMode::Ignore => {} // Sized indirect arguments - PassMode::Indirect { attrs, meta_attrs: None, on_stack: _ } => { + PassMode::Indirect { attrs, meta_attrs: None, address_space: _, mode: _ } => { let align = attrs.pointee_align.unwrap_or(self.layout.align.abi); OperandValue::Ref(PlaceValue::new_sized(val, align)).store(bx, dst); } // Unsized indirect arguments cannot be stored - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { bug!("unsized `ArgAbi` cannot be stored"); } PassMode::Cast { cast, pad_i32_count: _ } => { @@ -303,11 +303,11 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> { PassMode::Pair(..) => { OperandValue::Pair(next(), next()).store(bx, dst); } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { bug!("unsized `ArgAbi` cannot be stored"); } PassMode::Direct(_) - | PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } + | PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } | PassMode::Cast { .. } => { let next_arg = next(); self.store(bx, next_arg, dst); @@ -368,8 +368,13 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { PassMode::Ignore => cx.type_void(), PassMode::Direct(_) | PassMode::Pair(..) => self.ret.layout.immediate_llvm_type(cx), PassMode::Cast { cast, pad_i32_count: _ } => cast.llvm_type(cx), - PassMode::Indirect { .. } => { - llargument_tys.push(cx.type_ptr()); + PassMode::Indirect { address_space, .. } => { + let ty = if let Some(address_space) = address_space { + cx.type_ptr_ext(*address_space) + } else { + cx.type_ptr() + }; + llargument_tys.push(ty); cx.type_void() } }; @@ -394,7 +399,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 1, true)); continue; } - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { // Construct the type of a (wide) pointer to `ty`, and pass its two fields. // Any two ABI-compatible unsized types have the same metadata type and // moreover the same metadata value leads to the same dynamic size and @@ -405,7 +410,13 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { llargument_tys.push(ptr_layout.scalar_pair_element_llvm_type(cx, 1, true)); continue; } - PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => cx.type_ptr(), + PassMode::Indirect { attrs: _, meta_attrs: None, address_space, mode: _ } => { + if let Some(address_space) = address_space { + cx.type_ptr_ext(*address_space) + } else { + cx.type_ptr() + } + } PassMode::Cast { cast, pad_i32_count } => { // Add padding. llargument_tys.extend(std::iter::repeat_n( @@ -495,8 +506,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { apply_range_attr(llvm::AttributePlace::ReturnValue, scalar); } } - PassMode::Indirect { attrs, meta_attrs: _, on_stack } => { - assert!(!on_stack); + PassMode::Indirect { attrs, meta_attrs: _, address_space: _, mode } => { + assert!(*mode == IndirectMode::Pointer); let i = apply(attrs); let sret = llvm::CreateStructRetAttr( cx.llcx, @@ -522,7 +533,12 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { for arg in self.args.iter() { match &arg.mode { PassMode::Ignore => {} - PassMode::Indirect { attrs, meta_attrs: None, on_stack: true } => { + PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::OnStack, + } => { let i = apply(attrs); let byval = llvm::CreateByValAttr( cx.llcx, @@ -530,13 +546,31 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { ); attributes::apply_to_llfn(llfn, llvm::AttributePlace::Argument(i), &[byval]); } + PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::AmdgpuKernelArg, + } => { + let i = apply(attrs); + let byref = llvm::CreateByRefAttr( + cx.llcx, + cx.type_array(cx.type_i8(), arg.layout.size.bytes()), + ); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Argument(i), &[byref]); + } PassMode::Direct(attrs) => { let i = apply(attrs); if let BackendRepr::Scalar(scalar) = arg.layout.backend_repr { apply_range_attr(llvm::AttributePlace::Argument(i), scalar); } } - PassMode::Indirect { attrs, meta_attrs: None, on_stack: false } => { + PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::Pointer, + } => { let i = apply(attrs); if cx.sess().opts.optimize != config::OptLevel::No { attributes::apply_to_llfn( @@ -546,8 +580,13 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { ); } } - PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack } => { - assert!(!on_stack); + PassMode::Indirect { + attrs, + meta_attrs: Some(meta_attrs), + address_space: _, + mode, + } => { + assert!(*mode == IndirectMode::Pointer); apply(attrs); apply(meta_attrs); } @@ -625,8 +664,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { PassMode::Direct(attrs) => { attrs.apply_attrs_to_callsite(llvm::AttributePlace::ReturnValue, bx.cx, callsite); } - PassMode::Indirect { attrs, meta_attrs: _, on_stack } => { - assert!(!on_stack); + PassMode::Indirect { attrs, meta_attrs: _, address_space: _, mode } => { + assert!(*mode == IndirectMode::Pointer); let i = apply(bx.cx, attrs); let sret = llvm::CreateStructRetAttr( bx.cx.llcx, @@ -646,7 +685,12 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { for arg in self.args.iter() { match &arg.mode { PassMode::Ignore => {} - PassMode::Indirect { attrs, meta_attrs: None, on_stack: true } => { + PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::OnStack, + } => { let i = apply(bx.cx, attrs); let byval = llvm::CreateByValAttr( bx.cx.llcx, @@ -658,11 +702,38 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { &[byval], ); } + PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::AmdgpuKernelArg, + } => { + let i = apply(bx.cx, attrs); + let byref = llvm::CreateByRefAttr( + bx.cx.llcx, + bx.cx.type_array(bx.cx.type_i8(), arg.layout.size.bytes()), + ); + attributes::apply_to_callsite( + callsite, + llvm::AttributePlace::Argument(i), + &[byref], + ); + } PassMode::Direct(attrs) - | PassMode::Indirect { attrs, meta_attrs: None, on_stack: false } => { + | PassMode::Indirect { + attrs, + meta_attrs: None, + address_space: _, + mode: IndirectMode::Pointer, + } => { apply(bx.cx, attrs); } - PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack: _ } => { + PassMode::Indirect { + attrs, + meta_attrs: Some(meta_attrs), + address_space: _, + mode: _, + } => { apply(bx.cx, attrs); apply(bx.cx, meta_attrs); } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index d1cdf7bada0b1..63fcdf8dcbd9c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2015,6 +2015,7 @@ unsafe extern "C" { pub(crate) fn LLVMRustCreateDereferenceableAttr(C: &Context, bytes: u64) -> &Attribute; pub(crate) fn LLVMRustCreateDereferenceableOrNullAttr(C: &Context, bytes: u64) -> &Attribute; pub(crate) fn LLVMRustCreateByValAttr<'a>(C: &'a Context, ty: &'a Type) -> &'a Attribute; + pub(crate) fn LLVMRustCreateByRefAttr<'a>(C: &'a Context, ty: &'a Type) -> &'a Attribute; pub(crate) fn LLVMRustCreateStructRetAttr<'a>(C: &'a Context, ty: &'a Type) -> &'a Attribute; pub(crate) fn LLVMRustCreateElementTypeAttr<'a>(C: &'a Context, ty: &'a Type) -> &'a Attribute; pub(crate) fn LLVMRustCreateUWTableAttr(C: &Context, async_: bool) -> &Attribute; diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index 5452f4abc5c33..89e4d60656d34 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -122,6 +122,10 @@ pub(crate) fn CreateByValAttr<'ll>(llcx: &'ll Context, ty: &'ll Type) -> &'ll At unsafe { LLVMRustCreateByValAttr(llcx, ty) } } +pub(crate) fn CreateByRefAttr<'ll>(llcx: &'ll Context, ty: &'ll Type) -> &'ll Attribute { + unsafe { LLVMRustCreateByRefAttr(llcx, ty) } +} + pub(crate) fn CreateStructRetAttr<'ll>(llcx: &'ll Context, ty: &'ll Type) -> &'ll Attribute { unsafe { LLVMRustCreateStructRetAttr(llcx, ty) } } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 6b0def4ffa182..f99009a0f4243 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -18,7 +18,7 @@ use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::{self, Instance, Ty, TypeVisitableExt}; use rustc_session::config::OptLevel; use rustc_span::{Span, Spanned, bug, span_bug}; -use rustc_target::callconv::{ArgAbi, ArgAttributes, CastTarget, FnAbi, PassMode}; +use rustc_target::callconv::{ArgAbi, ArgAttributes, CastTarget, FnAbi, IndirectMode, PassMode}; use tracing::{debug, info}; use super::operand::OperandRef; @@ -1257,7 +1257,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { (args, None) }; - // Special logic for tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments. + // Special logic for tail calls with `PassMode::Indirect { mode: IndirectMode::Pointer, .. }` arguments. // // Normally an indirect argument that is allocated in the caller's stack frame // would be passed as a pointer into the callee's stack frame. @@ -1282,10 +1282,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let mut tail_call_temporaries = vec![]; if kind == CallKind::Tail { tail_call_temporaries = vec![None; first_args.len()]; - // Copy the arguments that use `PassMode::Indirect { on_stack: false , ..}` + // Copy the arguments that use `PassMode::Indirect { mode: IndirectMode::Pointer , ..}` // to temporary stack allocations. See the comment above. for (i, arg) in first_args.iter().enumerate() { - if !matches!(fn_abi.args[i].mode, PassMode::Indirect { on_stack: false, .. }) { + if !matches!( + fn_abi.args[i].mode, + PassMode::Indirect { mode: IndirectMode::Pointer, .. } + ) { continue; } @@ -1353,10 +1356,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } - let by_move = if let PassMode::Indirect { on_stack: false, .. } = fn_abi.args[i].mode + let by_move = if let PassMode::Indirect { mode: IndirectMode::Pointer, .. } = + fn_abi.args[i].mode && kind == CallKind::Tail { - // Special logic for tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments. + // Special logic for tail calls with `PassMode::Indirect { mode: IndirectMode::Pointer, .. }` arguments. // // Normally an indirect argument that is allocated in the caller's stack frame // would be passed as a pointer into the callee's stack frame. @@ -1977,14 +1981,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } _ => bug!("codegen_argument: {:?} invalid for pair argument", op), }, - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => match op.val { - Ref(PlaceValue { llval: a, llextra: Some(b), .. }) => { - llargs.push(a); - llargs.push(b); - return; + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { + match op.val { + Ref(PlaceValue { llval: a, llextra: Some(b), .. }) => { + llargs.push(a); + llargs.push(b); + return; + } + _ => bug!("codegen_argument: {:?} invalid for unsized indirect argument", op), } - _ => bug!("codegen_argument: {:?} invalid for unsized indirect argument", op), - }, + } _ => {} } @@ -2014,7 +2020,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { PassMode::Ignore | PassMode::Pair(..) => unreachable!("handled above"), }, Ref(op_place_val) => match arg.mode { - PassMode::Indirect { attrs, on_stack, .. } => { + PassMode::Indirect { attrs, mode, .. } => { + if mode == IndirectMode::AmdgpuKernelArg { + bug!("{op:?} passed as amdgpu kernel argument with abi {arg:?}"); + } // For `foo(packed.large_field)`, and types with <4 byte alignment on x86, // alignment requirements may be higher than the type's alignment, so copy // to a higher-aligned alloca. @@ -2023,7 +2032,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { None => arg.layout.align.abi, }; // Copy to an alloca when the argument is neither by-val nor by-move. - if op_place_val.align < required_align || (!on_stack && !by_move) { + if op_place_val.align < required_align + || (mode == IndirectMode::Pointer && !by_move) + { let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align); bx.lifetime_start(scratch.llval, arg.layout.size); op.store_with_annotation(bx, scratch.with_type(arg.layout)); @@ -2036,8 +2047,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => (op_place_val.llval, op_place_val.align, true), }, ZeroSized => match arg.mode { - PassMode::Indirect { on_stack, .. } => { - if on_stack { + PassMode::Indirect { mode, .. } => { + if mode == IndirectMode::AmdgpuKernelArg { + bug!("{op:?} passed as amdgpu kernel argument with abi {arg:?}"); + } + if mode == IndirectMode::OnStack { // It doesn't seem like any target can have `byval` ZSTs, so this assert // is here to replace a would-be untested codepath. bug!("ZST {op:?} passed on stack with abi {arg:?}"); diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index b5cecf4b5c434..aefa8356536dc 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -8,7 +8,7 @@ use rustc_middle::mir::{Body, Local, UnwindTerminateReason, traversal}; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_span::{ErrorGuaranteed, bug, span_bug}; -use rustc_target::callconv::{FnAbi, PassMode}; +use rustc_target::callconv::{FnAbi, IndirectMode, PassMode}; use tracing::{debug, instrument}; use crate::base; @@ -561,15 +561,21 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( match arg.mode { // Sized indirect arguments - PassMode::Indirect { attrs, meta_attrs: None, on_stack: _ } => { + PassMode::Indirect { attrs, meta_attrs: None, address_space: _, mode } => { // Don't copy an indirect argument to an alloca, the caller already put it // in a temporary alloca and gave it up. + // AmdgpuKernelArg/byref arguments must not be modified, so always create a + // local alloca for them. + // If the argument is underaligned, then we need to copy it to a higher-aligned + // alloca. // FIXME: lifetimes + let mut needs_alloca = mode == IndirectMode::AmdgpuKernelArg; if let Some(pointee_align) = attrs.pointee_align && pointee_align < arg.layout.align.abi { - // ...unless the argument is underaligned, then we need to copy it to - // a higher-aligned alloca. + needs_alloca = true; + } + if needs_alloca { let tmp = PlaceRef::alloca(bx, arg.layout); bx.store_fn_arg(arg, &mut llarg_idx, tmp); LocalRef::Place(tmp) @@ -580,7 +586,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } } // Unsized indirect arguments - PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => { + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } => { // As the storage for the indirect argument lives during // the whole function call, we just copy the wide pointer. let llarg = bx.get_param(llarg_idx); diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 161b5bdb952d3..bc8fa60b66a52 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -480,6 +480,11 @@ extern "C" LLVMAttributeRef LLVMRustCreateByValAttr(LLVMContextRef C, return wrap(Attribute::getWithByValType(*unwrap(C), unwrap(Ty))); } +extern "C" LLVMAttributeRef LLVMRustCreateByRefAttr(LLVMContextRef C, + LLVMTypeRef Ty) { + return wrap(Attribute::getWithByRefType(*unwrap(C), unwrap(Ty))); +} + extern "C" LLVMAttributeRef LLVMRustCreateStructRetAttr(LLVMContextRef C, LLVMTypeRef Ty) { return wrap(Attribute::getWithStructRetType(*unwrap(C), unwrap(Ty))); diff --git a/compiler/rustc_mir_transform/src/deduce_param_attrs.rs b/compiler/rustc_mir_transform/src/deduce_param_attrs.rs index 5bba125aefc58..8814670ca4300 100644 --- a/compiler/rustc_mir_transform/src/deduce_param_attrs.rs +++ b/compiler/rustc_mir_transform/src/deduce_param_attrs.rs @@ -135,7 +135,7 @@ impl<'tcx> Visitor<'tcx> for DeduceParamAttrs { } // Like a call, but more conservative because the backend may introduce writes to an - // argument if the argument is passed as `PassMode::Indirect { on_stack: false, ... }`. + // argument if the argument is passed as `PassMode::Indirect { mode: IndirectMode::Pointer, ... }`. TerminatorKind::TailCall { .. } => { for usage in self.usage.iter_mut() { *usage |= UsageSummary::MUTATE; diff --git a/compiler/rustc_public/src/abi.rs b/compiler/rustc_public/src/abi.rs index b760ed98c7111..c6d1d2c77a13f 100644 --- a/compiler/rustc_public/src/abi.rs +++ b/compiler/rustc_public/src/abi.rs @@ -41,6 +41,19 @@ pub struct ArgAbi { pub mode: PassMode, } +/// Different modes in which indirect arguments can be passed. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize)] +pub enum IndirectMode { + /// Passed as a normal pointer, nothing special. + Pointer, + /// The value is placed at a fixed stack offset rather than passed as a regular pointer + /// argument. + OnStack, + /// Similar to `OnStack` except that the pointer does not necessarily point to the stack, no + /// extra copy is made, and the passed argument should not be modified. + AmdgpuKernelArg, +} + /// How a function argument should be passed in to the target function. /// /// The pass mode is determined by the platform's calling convention and the @@ -74,14 +87,13 @@ pub enum PassMode { /// Pass the argument indirectly via a pointer. /// /// The caller places the value in memory and passes a pointer to it. - /// When `on_stack` is true, the value is placed at a fixed stack offset - /// rather than passed as a regular pointer argument. Indirect { attrs: ArgAttributes, /// Attributes for the metadata pointer (vtable or length) of unsized arguments. /// Only present for unsized types (e.g., `dyn Trait`, `[T]`). meta_attrs: Option, - on_stack: bool, + address_space: Option, + mode: IndirectMode, }, } diff --git a/compiler/rustc_public/src/unstable/convert/stable/abi.rs b/compiler/rustc_public/src/unstable/convert/stable/abi.rs index 766c522958db7..65b8e9bd72761 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/abi.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/abi.rs @@ -11,9 +11,9 @@ use rustc_target::callconv; use crate::IndexedVal; use crate::abi::{ AddressSpace, ArgAbi, ArgAttributes, ArgExtension, CallConvention, CastTarget, FieldsShape, - FloatLength, FnAbi, IntegerLength, IntegerType, Layout, LayoutShape, NumScalableVectors, - PassMode, Primitive, Reg, RegKind, ReprFlags, ReprOptions, Scalar, TagEncoding, TyAndLayout, - Uniform, ValueRepr, VariantFields, VariantsShape, WrappingRange, + FloatLength, FnAbi, IndirectMode, IntegerLength, IntegerType, Layout, LayoutShape, + NumScalableVectors, PassMode, Primitive, Reg, RegKind, ReprFlags, ReprOptions, Scalar, + TagEncoding, TyAndLayout, Uniform, ValueRepr, VariantFields, VariantsShape, WrappingRange, }; use crate::compiler_interface::BridgeTys; use crate::target::MachineSize as Size; @@ -155,6 +155,22 @@ impl<'tcx> Stable<'tcx> for CanonAbi { } } +impl<'tcx> Stable<'tcx> for callconv::IndirectMode { + type T = IndirectMode; + + fn stable<'cx>( + &self, + _tables: &mut Tables<'cx, BridgeTys>, + _cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + match self { + callconv::IndirectMode::Pointer => IndirectMode::Pointer, + callconv::IndirectMode::OnStack => IndirectMode::OnStack, + callconv::IndirectMode::AmdgpuKernelArg => IndirectMode::AmdgpuKernelArg, + } + } +} + impl<'tcx> Stable<'tcx> for callconv::PassMode { type T = PassMode; @@ -172,11 +188,14 @@ impl<'tcx> Stable<'tcx> for callconv::PassMode { callconv::PassMode::Cast { pad_i32_count, cast } => { PassMode::Cast { pad_i32_count: *pad_i32_count, cast: cast.stable(tables, cx) } } - callconv::PassMode::Indirect { attrs, meta_attrs, on_stack } => PassMode::Indirect { - attrs: attrs.stable(tables, cx), - meta_attrs: meta_attrs.map(|a| a.stable(tables, cx)), - on_stack: *on_stack, - }, + callconv::PassMode::Indirect { attrs, meta_attrs, address_space, mode } => { + PassMode::Indirect { + attrs: attrs.stable(tables, cx), + meta_attrs: meta_attrs.map(|a| a.stable(tables, cx)), + address_space: address_space.stable(tables, cx), + mode: mode.stable(tables, cx), + } + } } } } diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index 9fe22a3a174b6..474f45b54e9b2 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -36,6 +36,25 @@ mod x86_win32; mod x86_win64; mod xtensa; +/// Different modes in which indirect arguments can be passed. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, StableHash)] +pub enum IndirectMode { + /// Passed as a normal pointer, nothing special. + Pointer, + /// The value should be passed at a fixed stack offset in accordance to + /// the ABI rather than passed using a pointer. This corresponds to the `byval` LLVM argument + /// attribute. The `byval` argument will use a byte array with the same size as the Rust type + /// (which ensures that padding is preserved and that we do not rely on LLVM's struct layout), + /// and will use the alignment specified in `attrs.pointee_align` (if `Some`) or the type's + /// alignment (if `None`). This means that the alignment will not always + /// match the Rust type's alignment; see documentation of `pass_by_stack_offset` for more info. + OnStack, + /// `AmdgpuKernelArg` behaves similar to `OnStack` except that the pointer does not necessarily + /// point to the stack, no extra copy is made, and the passed argument should not be modified. + /// This corresponds to the `byref` LLVM argument attribute. + AmdgpuKernelArg, +} + #[derive(Clone, PartialEq, Eq, Hash, Debug, StableHash)] pub enum PassMode { /// Ignore the argument. @@ -63,16 +82,17 @@ pub enum PassMode { /// The `meta_attrs` value, if any, is for the metadata (vtable or length) of an unsized /// argument. (This is the only mode that supports unsized arguments.) /// - /// `on_stack` defines that the value should be passed at a fixed stack offset in accordance to - /// the ABI rather than passed using a pointer. This corresponds to the `byval` LLVM argument - /// attribute. The `byval` argument will use a byte array with the same size as the Rust type - /// (which ensures that padding is preserved and that we do not rely on LLVM's struct layout), - /// and will use the alignment specified in `attrs.pointee_align` (if `Some`) or the type's - /// alignment (if `None`). This means that the alignment will not always - /// match the Rust type's alignment; see documentation of `pass_by_stack_offset` for more info. + /// `address_space` specifies if the pointer is in a special address space or the default one. /// - /// `on_stack` cannot be true for unsized arguments, i.e., when `meta_attrs` is `Some`. - Indirect { attrs: ArgAttributes, meta_attrs: Option, on_stack: bool }, + /// `mode` can be a special way to pass an argument indirectly. + /// `OnStack` and `AmdgpuKernelArg` cannot be used for unsized arguments, i.e., when + /// `meta_attrs` is `Some`. + Indirect { + attrs: ArgAttributes, + meta_attrs: Option, + address_space: Option, + mode: IndirectMode, + }, } impl PassMode { @@ -89,13 +109,23 @@ impl PassMode { PassMode::Cast { cast: c2, pad_i32_count: pad2 }, ) => c1.eq_abi(c2) && pad1 == pad2, ( - PassMode::Indirect { attrs: a1, meta_attrs: None, on_stack: s1 }, - PassMode::Indirect { attrs: a2, meta_attrs: None, on_stack: s2 }, - ) => a1.eq_abi(a2) && s1 == s2, + PassMode::Indirect { attrs: a1, meta_attrs: None, address_space: as1, mode: m1 }, + PassMode::Indirect { attrs: a2, meta_attrs: None, address_space: as2, mode: m2 }, + ) => a1.eq_abi(a2) && as1 == as2 && m1 == m2, ( - PassMode::Indirect { attrs: a1, meta_attrs: Some(e1), on_stack: s1 }, - PassMode::Indirect { attrs: a2, meta_attrs: Some(e2), on_stack: s2 }, - ) => a1.eq_abi(a2) && e1.eq_abi(e2) && s1 == s2, + PassMode::Indirect { + attrs: a1, + meta_attrs: Some(e1), + address_space: as1, + mode: m1, + }, + PassMode::Indirect { + attrs: a2, + meta_attrs: Some(e2), + address_space: as2, + mode: m2, + }, + ) => a1.eq_abi(a2) && as1 == as2 && e1.eq_abi(e2) && m1 == m2, _ => false, } } @@ -424,7 +454,7 @@ impl<'a, Ty> ArgAbi<'a, Ty> { let meta_attrs = layout.is_unsized().then_some(ArgAttributes::new()); - PassMode::Indirect { attrs, meta_attrs, on_stack: false } + PassMode::Indirect { attrs, meta_attrs, address_space: None, mode: IndirectMode::Pointer } } /// Pass this argument indirectly, by passing a (thin or wide) pointer to the argument instead. @@ -435,13 +465,31 @@ impl<'a, Ty> ArgAbi<'a, Ty> { PassMode::Direct(_) | PassMode::Pair(_, _) => { self.mode = Self::indirect_pass_mode(&self.layout); } - PassMode::Indirect { attrs: _, meta_attrs: _, on_stack: false } => { + PassMode::Indirect { + attrs: _, + meta_attrs: _, + address_space: _, + mode: IndirectMode::Pointer, + } => { // already indirect } _ => panic!("Tried to make {:?} indirect", self.mode), } } + /// Pass this argument indirectly, by passing a (thin or wide) pointer to the argument instead. + /// This is valid for both sized and unsized arguments. + #[track_caller] + pub fn make_indirect_addrspace(&mut self, addrspace: AddressSpace) { + self.make_indirect(); + match self.mode { + PassMode::Indirect { ref mut address_space, .. } => { + *address_space = Some(addrspace); + } + _ => unreachable!(), + } + } + /// Same as `make_indirect`, but for arguments that are ignored. Only needed for ABIs that pass /// ZSTs indirectly. #[track_caller] @@ -450,7 +498,12 @@ impl<'a, Ty> ArgAbi<'a, Ty> { PassMode::Ignore => { self.mode = Self::indirect_pass_mode(&self.layout); } - PassMode::Indirect { attrs: _, meta_attrs: _, on_stack: false } => { + PassMode::Indirect { + attrs: _, + meta_attrs: _, + address_space: _, + mode: IndirectMode::Pointer, + } => { // already indirect } _ => panic!("Tried to make {:?} indirect (expected `PassMode::Ignore`)", self.mode), @@ -477,8 +530,8 @@ impl<'a, Ty> ArgAbi<'a, Ty> { assert!(!self.layout.is_unsized(), "used byval ABI for unsized layout"); self.make_indirect(); match self.mode { - PassMode::Indirect { ref mut attrs, meta_attrs: _, ref mut on_stack } => { - *on_stack = true; + PassMode::Indirect { ref mut attrs, meta_attrs: _, address_space: _, ref mut mode } => { + *mode = IndirectMode::OnStack; // Some platforms, like 32-bit x86, change the alignment of the type when passing // `byval`. Account for that. @@ -492,6 +545,22 @@ impl<'a, Ty> ArgAbi<'a, Ty> { } } + /// Pass this argument indirectly. + /// This corresponds to the `byref` LLVM argument attribute. + /// + /// `address_space` specifies the address space of the passed pointer. + pub fn pass_amdgpu_kernel_arg(&mut self, addrspace: Option) { + assert!(!self.layout.is_unsized(), "used amdgpu kernel arg ABI for unsized layout"); + self.make_indirect(); + match self.mode { + PassMode::Indirect { attrs: _, meta_attrs: _, ref mut address_space, ref mut mode } => { + *mode = IndirectMode::AmdgpuKernelArg; + *address_space = addrspace; + } + _ => unreachable!(), + } + } + pub fn extend_integer_width_to(&mut self, bits: u64) { // Only integers have signedness if let BackendRepr::Scalar(scalar) = self.layout.backend_repr @@ -545,11 +614,17 @@ impl<'a, Ty> ArgAbi<'a, Ty> { } pub fn is_sized_indirect(&self) -> bool { - matches!(self.mode, PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ }) + matches!( + self.mode, + PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } + ) } pub fn is_unsized_indirect(&self) -> bool { - matches!(self.mode, PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ }) + matches!( + self.mode, + PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } + ) } pub fn is_ignore(&self) -> bool { @@ -834,7 +909,7 @@ impl<'a, Ty> FnAbi<'a, Ty> { // Compute `Aggregate` ABI. let is_indirect_not_on_stack = - matches!(arg.mode, PassMode::Indirect { on_stack: false, .. }); + matches!(arg.mode, PassMode::Indirect { mode: IndirectMode::Pointer, .. }); assert!(is_indirect_not_on_stack); let size = arg.layout.size; @@ -949,7 +1024,7 @@ mod size_asserts { use super::*; // tidy-alphabetical-start - static_assert_size!(ArgAbi<'_, usize>, 56); - static_assert_size!(FnAbi<'_, usize>, 80); + static_assert_size!(ArgAbi<'_, usize>, 64); + static_assert_size!(FnAbi<'_, usize>, 88); // tidy-alphabetical-end } diff --git a/compiler/rustc_target/src/callconv/x86.rs b/compiler/rustc_target/src/callconv/x86.rs index fd608fcf62919..f51e29b34e1d3 100644 --- a/compiler/rustc_target/src/callconv/x86.rs +++ b/compiler/rustc_target/src/callconv/x86.rs @@ -167,12 +167,13 @@ pub(crate) fn fill_inregs<'a, Ty, C>( for arg in fn_abi.args.iter_mut() { let attrs = match arg.mode { - PassMode::Ignore | PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => { + PassMode::Ignore + | PassMode::Indirect { attrs: _, meta_attrs: None, address_space: _, mode: _ } => { continue; } PassMode::Direct(ref mut attrs) => attrs, PassMode::Pair(..) - | PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } + | PassMode::Indirect { attrs: _, meta_attrs: Some(_), address_space: _, mode: _ } | PassMode::Cast { .. } => { unreachable!("x86 shouldn't be passing arguments by {:?}", arg.mode) } diff --git a/compiler/rustc_target/src/callconv/xtensa.rs b/compiler/rustc_target/src/callconv/xtensa.rs index 4dc9fad650636..49005adeb33c0 100644 --- a/compiler/rustc_target/src/callconv/xtensa.rs +++ b/compiler/rustc_target/src/callconv/xtensa.rs @@ -7,7 +7,7 @@ use rustc_abi::{BackendRepr, HasDataLayout, Size, TyAbiInterface}; -use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; +use crate::callconv::{ArgAbi, FnAbi, IndirectMode, Reg, Uniform}; use crate::spec::HasTargetSpec; const NUM_ARG_GPRS: u64 = 6; @@ -29,8 +29,8 @@ where classify_arg_ty(cx, arg, &mut arg_gprs_left, true); // Ret args cannot be passed via stack, we lower to indirect and let the backend handle the invisible reference match arg.mode { - super::PassMode::Indirect { attrs: _, meta_attrs: _, ref mut on_stack } => { - *on_stack = false; + super::PassMode::Indirect { attrs: _, meta_attrs: _, address_space: _, ref mut mode } => { + *mode = IndirectMode::Pointer; } _ => {} } diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 55140d2c5458d..e8f9ded9562d5 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -12,7 +12,9 @@ use rustc_middle::ty::layout::{ use rustc_middle::ty::{self, InstanceKind, ShimKind, Ty, TyCtxt, Unnormalized}; use rustc_span::def_id::DefId; use rustc_span::{DUMMY_SP, bug}; -use rustc_target::callconv::{AbiMap, ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMode}; +use rustc_target::callconv::{ + AbiMap, ArgAbi, ArgAttribute, ArgAttributes, FnAbi, IndirectMode, PassMode, +}; use tracing::debug; pub(crate) fn provide(providers: &mut Providers) { @@ -444,15 +446,15 @@ fn fn_abi_sanity_check<'tcx>( // omitted entirely in the calling convention. assert!(arg.is_ignore()); } - if let PassMode::Indirect { on_stack, .. } = arg.mode + if let PassMode::Indirect { mode, .. } = arg.mode && spec_abi != ExternAbi::RustTail { - assert!(!on_stack, "rustic abi {spec_abi:?} shouldn't use on_stack"); + assert!(mode == IndirectMode::Pointer, "rust abi must use plain pointer mode"); } } else if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { assert_matches!( arg.mode, - PassMode::Indirect { on_stack: false, .. }, + PassMode::Indirect { mode: IndirectMode::Pointer, .. }, "the {spec_abi} ABI does not implement `#[rustc_pass_indirectly_in_non_rustic_abis]`" ); } @@ -506,9 +508,9 @@ fn fn_abi_sanity_check<'tcx>( // Indirect returns are arguments from an ABI perspective. fn_arg_attrs_sanity_check(attrs, false); } - PassMode::Indirect { meta_attrs: Some(meta_attrs), attrs, on_stack } => { + PassMode::Indirect { meta_attrs: Some(meta_attrs), attrs, address_space: _, mode } => { // With metadata. Must be unsized and not on the stack. - assert!(arg.layout.is_unsized() && !on_stack); + assert!(arg.layout.is_unsized() && *mode == IndirectMode::Pointer); // Also, must not be `extern` type. let tail = tcx.struct_tail_for_codegen(arg.layout.ty, cx.typing_env); if matches!(tail.kind(), ty::Foreign(..)) { diff --git a/tests/assembly-llvm/tail-call-indirect.rs b/tests/assembly-llvm/tail-call-indirect.rs index 2bc1743a9bafd..918283966b405 100644 --- a/tests/assembly-llvm/tail-call-indirect.rs +++ b/tests/assembly-llvm/tail-call-indirect.rs @@ -10,10 +10,10 @@ #![no_core] #![crate_type = "lib"] -// Test tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments. +// Test tail calls with `PassMode::Indirect { mode: IndirectMode::Pointer, .. }` arguments. // -// Normally an indirect argument with `on_stack: false` would be passed as a pointer to the -// caller's stack frame. For tail calls, that would be unsound, because the caller's stack +// Normally an indirect argument with `mode: IndirectMode::Pointer` would be passed as a pointer to +// the caller's stack frame. For tail calls, that would be unsound, because the caller's stack // frame is overwritten by the callee's stack frame. // // The solution is to write the argument into the caller's argument place (stored somewhere further diff --git a/tests/ui-fulldeps/rustc_public/check_abi.rs b/tests/ui-fulldeps/rustc_public/check_abi.rs index f6c95fb745409..92312cd4c8712 100644 --- a/tests/ui-fulldeps/rustc_public/check_abi.rs +++ b/tests/ui-fulldeps/rustc_public/check_abi.rs @@ -15,8 +15,8 @@ extern crate rustc_middle; extern crate rustc_public; use rustc_public::abi::{ - ArgAbi, ArgExtension, CallConvention, FieldsShape, IntegerLength, PassMode, Primitive, Scalar, - ValueRepr, VariantsShape, + ArgAbi, ArgExtension, CallConvention, FieldsShape, IndirectMode, IntegerLength, PassMode, + Primitive, Scalar, ValueRepr, VariantsShape, }; use rustc_public::mir::MirVisitor; use rustc_public::mir::mono::Instance; @@ -122,14 +122,14 @@ fn check_primitive(abi: &ArgAbi) { /// Check the return value: `Result`. fn check_result(abi: &ArgAbi) { assert!(abi.ty.kind().is_enum()); - let PassMode::Indirect { ref attrs, ref meta_attrs, on_stack } = abi.mode else { + let PassMode::Indirect { ref attrs, ref meta_attrs, address_space: _, mode } = abi.mode else { panic!("Expected PassMode::Indirect for Result, got: {:?}", abi.mode); }; // Indirect arguments have a pointee alignment (the pointer must be aligned). assert!(attrs.pointee_align().is_some()); // Result is a sized type, so no metadata pointer. assert!(meta_attrs.is_none()); - assert!(!on_stack); + assert!(mode == IndirectMode::Pointer); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert_matches!(layout.fields, FieldsShape::Arbitrary { .. }); diff --git a/tests/ui-fulldeps/rustc_public/check_abi_cast.rs b/tests/ui-fulldeps/rustc_public/check_abi_cast.rs index 0bd4ac684066e..a54abdd5deeaf 100644 --- a/tests/ui-fulldeps/rustc_public/check_abi_cast.rs +++ b/tests/ui-fulldeps/rustc_public/check_abi_cast.rs @@ -23,7 +23,7 @@ use std::convert::TryFrom; use std::io::Write; use std::ops::ControlFlow; -use rustc_public::abi::{CallConvention, PassMode, RegKind}; +use rustc_public::abi::{CallConvention, IndirectMode, PassMode, RegKind}; use rustc_public::mir::mono::Instance; use rustc_public::{CrateDef, ItemKind}; @@ -147,7 +147,7 @@ fn test_abi_cast() -> ControlFlow<()> { } // Fourth TwoWords has no registers left → Indirect (on stack) assert!( - matches!(&abi.args[3].mode, PassMode::Indirect { on_stack: true, .. }), + matches!(&abi.args[3].mode, PassMode::Indirect { mode: IndirectMode::OnStack, .. }), "Expected arg 3 to be Indirect on stack, got: {:?}", abi.args[3].mode ); diff --git a/tests/ui/abi/c-zst.powerpc-linux.stderr b/tests/ui/abi/c-zst.powerpc-linux.stderr index edea2d5772280..cefc2ba085182 100644 --- a/tests/ui/abi/c-zst.powerpc-linux.stderr +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -35,7 +35,8 @@ error: fn_abi_of(pass_zst) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/c-zst.s390x-linux.stderr b/tests/ui/abi/c-zst.s390x-linux.stderr index edea2d5772280..cefc2ba085182 100644 --- a/tests/ui/abi/c-zst.s390x-linux.stderr +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -35,7 +35,8 @@ error: fn_abi_of(pass_zst) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/c-zst.sparc64-linux.stderr b/tests/ui/abi/c-zst.sparc64-linux.stderr index edea2d5772280..cefc2ba085182 100644 --- a/tests/ui/abi/c-zst.sparc64-linux.stderr +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -35,7 +35,8 @@ error: fn_abi_of(pass_zst) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr index edea2d5772280..cefc2ba085182 100644 --- a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr +++ b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr @@ -35,7 +35,8 @@ error: fn_abi_of(pass_zst) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/debug.generic.stderr b/tests/ui/abi/debug.generic.stderr index 6242d93b09534..1793674fa462a 100644 --- a/tests/ui/abi/debug.generic.stderr +++ b/tests/ui/abi/debug.generic.stderr @@ -446,7 +446,8 @@ error: ABIs are not compatible ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], @@ -519,7 +520,8 @@ error: ABIs are not compatible ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/debug.loongarch64.stderr b/tests/ui/abi/debug.loongarch64.stderr index 176c68ecd4c7b..29ec7846101f1 100644 --- a/tests/ui/abi/debug.loongarch64.stderr +++ b/tests/ui/abi/debug.loongarch64.stderr @@ -446,7 +446,8 @@ error: ABIs are not compatible ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], @@ -519,7 +520,8 @@ error: ABIs are not compatible ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/debug.riscv64.stderr b/tests/ui/abi/debug.riscv64.stderr index 176c68ecd4c7b..29ec7846101f1 100644 --- a/tests/ui/abi/debug.riscv64.stderr +++ b/tests/ui/abi/debug.riscv64.stderr @@ -446,7 +446,8 @@ error: ABIs are not compatible ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], @@ -519,7 +520,8 @@ error: ABIs are not compatible ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/abi/pass-indirectly-attr.rs b/tests/ui/abi/pass-indirectly-attr.rs index 54aafc716587c..bb90b8354ea91 100644 --- a/tests/ui/abi/pass-indirectly-attr.rs +++ b/tests/ui/abi/pass-indirectly-attr.rs @@ -20,7 +20,7 @@ pub struct Type(u8); pub extern "C" fn extern_c(_: Type) {} //~^ ERROR fn_abi_of(extern_c) = FnAbi { //~| ERROR mode: Indirect -//~| ERROR on_stack: false, +//~| ERROR mode: Pointer, //~| ERROR conv: C, #[rustc_abi(debug)] diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index efeec0d86982b..5821e6279bb85 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -48,7 +48,8 @@ error: fn_abi_of(extern_c) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr index 45edd7bc0e0ee..c9e77ac941901 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -35,7 +35,8 @@ error: fn_abi_of(take_va_list) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/c-variadic/pass-by-value-abi.rs b/tests/ui/c-variadic/pass-by-value-abi.rs index bcca09e90438a..317840601c050 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.rs +++ b/tests/ui/c-variadic/pass-by-value-abi.rs @@ -27,9 +27,9 @@ use std::ffi::VaList; pub extern "C" fn take_va_list(_: VaList<'_>) {} //~^ ERROR fn_abi_of(take_va_list) = FnAbi { //[x86_64]~^^ ERROR mode: Indirect { -//[x86_64]~^^^ ERROR on_stack: false, +//[x86_64]~^^^ ERROR mode: Pointer, //[aarch64]~^^^^ ERROR mode: Indirect { -//[aarch64]~^^^^^ ERROR on_stack: false, +//[aarch64]~^^^^^ ERROR mode: Pointer, //[win]~^^^^^^ ERROR mode: Direct( #[cfg(all(target_arch = "x86_64", not(windows)))] @@ -37,11 +37,11 @@ pub extern "C" fn take_va_list(_: VaList<'_>) {} pub extern "sysv64" fn take_va_list_sysv64(_: VaList<'_>) {} //[x86_64]~^ ERROR fn_abi_of(take_va_list_sysv64) = FnAbi { //[x86_64]~^^ ERROR mode: Indirect { -//[x86_64]~^^^ ERROR on_stack: false, +//[x86_64]~^^^ ERROR mode: Pointer, #[cfg(all(target_arch = "x86_64", not(windows)))] #[rustc_abi(debug)] pub extern "win64" fn take_va_list_win64(_: VaList<'_>) {} //[x86_64]~^ ERROR: fn_abi_of(take_va_list_win64) = FnAbi { //[x86_64]~^^ ERROR mode: Indirect { -//[x86_64]~^^^ ERROR on_stack: false, +//[x86_64]~^^^ ERROR mode: Pointer, diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr index 1e203b93e66b3..04320a5312361 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -35,7 +35,8 @@ error: fn_abi_of(take_va_list) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], @@ -113,7 +114,8 @@ error: fn_abi_of(take_va_list_sysv64) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], @@ -193,7 +195,8 @@ error: fn_abi_of(take_va_list_win64) = FnAbi { ), }, meta_attrs: None, - on_stack: false, + address_space: None, + mode: Pointer, }, }, ], diff --git a/tests/ui/explicit-tail-calls/indirect.rs b/tests/ui/explicit-tail-calls/indirect.rs index b3e2613efad25..71107ef420c35 100644 --- a/tests/ui/explicit-tail-calls/indirect.rs +++ b/tests/ui/explicit-tail-calls/indirect.rs @@ -25,17 +25,17 @@ #![feature(explicit_tail_calls)] #![expect(incomplete_features)] -// Test tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments. +// Test tail calls with `PassMode::Indirect { mode: IndirectMode::Pointer, .. }` arguments. // -// Normally an indirect argument with `on_stack: false` would be passed as a pointer to the -// caller's stack frame. For tail calls, that would be unsound, because the caller's stack +// Normally an indirect argument with `mode: IndirectMode::Pointer` would be passed as a pointer to +// the caller's stack frame. For tail calls, that would be unsound, because the caller's stack // frame is overwritten by the callee's stack frame. // // The solution is to write the argument into the caller's argument place (stored somewhere further // up the stack), and forward that place. // A struct big enough that it is not passed via registers, so that the rust calling convention uses -// `Indirect { on_stack: false, .. }`. +// `Indirect { mode: IndirectMode::Pointer, .. }`. #[repr(C)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub struct Big([u64; 4]); @@ -79,7 +79,7 @@ fn main() { assert_eq!(update_in_caller(Big::default()), 0 + 2 + 3 + 4); assert_eq!(swapper(u8::MIN, u8::MAX), (u8::MAX, u8::MIN)); - // i128 uses `PassMode::Indirect { on_stack: false, .. }` on x86_64 MSVC. + // i128 uses `PassMode::Indirect { mode: IndirectMode::Pointer, .. }` on x86_64 MSVC. assert_eq!(swapper(i128::MIN, i128::MAX), (i128::MAX, i128::MIN)); assert_eq!(swapper(Big([1; 4]), Big([2; 4])), (Big([2; 4]), Big([1; 4]))); From fff83c5b50993856835be07dda1e262c322af6f3 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Thu, 17 Sep 2026 17:47:51 +0000 Subject: [PATCH 20/76] Prepare for merging from rust-lang/rust This updates the rust-version file to c999cef531ea9059e189e82fe0e82c5daf249bc9. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index f7d3ea0b9325b..7c357c0e50e54 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -4b6d04e706108ccfeafe2547fbe857dfe8972bad +c999cef531ea9059e189e82fe0e82c5daf249bc9 From 677806f9aeb5d0cf206aeffb3e164fd7b93d1b8a Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Thu, 17 Sep 2026 21:01:31 +0100 Subject: [PATCH 21/76] Remove applying inline attributes at the callsite --- compiler/rustc_codegen_llvm/src/builder.rs | 15 ------- .../call-site-inline-attributes.rs | 40 ------------------- 2 files changed, 55 deletions(-) delete mode 100644 tests/codegen-llvm/call-site-inline-attributes.rs diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 9d4602e49968d..abc71f450a515 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -1523,21 +1523,6 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { ) }; - if let Some(callee_instance) = callee_instance { - // Attributes on the function definition being called - let callee_attrs = self.cx.tcx.codegen_fn_attrs(callee_instance.def_id()); - - if let Some(inlining_rule) = - attributes::inline_attr(&self.cx, self.cx.tcx, callee_instance, callee_attrs) - { - attributes::apply_to_callsite( - call, - llvm::AttributePlace::Function, - &[inlining_rule], - ); - } - } - if let Some(fn_abi) = fn_abi { fn_abi.apply_attrs_callsite(self, call); } diff --git a/tests/codegen-llvm/call-site-inline-attributes.rs b/tests/codegen-llvm/call-site-inline-attributes.rs deleted file mode 100644 index 01839526c50c1..0000000000000 --- a/tests/codegen-llvm/call-site-inline-attributes.rs +++ /dev/null @@ -1,40 +0,0 @@ -//@ compile-flags: -O -Zinline-mir=no -Cno-prepopulate-passes -Zmerge-functions=disabled - -#![crate_type = "lib"] - -// This test checks that we add inlinehint for #[inline], noinline for #[inline(never)], and -// alwaysinline for #[inline(always)] to call sites. - -#[unsafe(no_mangle)] -fn calls_something_noinline() { - // CHECK-LABEL @calls_something_noinline - // CHECK: call void @{{.*}}noinline_fn() #[[NOINLINE:[0-9]+]] - noinline_fn(); -} - -#[inline(never)] -fn noinline_fn() {} - -#[unsafe(no_mangle)] -fn calls_something_inline() { - // CHECK-LABEL @calls_something_inlinehint - // CHECK: call void @{{.*}}inlinehint_fn() #[[INLINEHINT:[0-9]+]] - inlinehint_fn(); -} - -#[inline] -fn inlinehint_fn() {} - -#[unsafe(no_mangle)] -fn calls_something_alwaysinline() { - // CHECK-LABEL @calls_something_alwaysinline - // CHECK: call void @{{.*}}alwaysinline_fn() #[[ALWAYSINLINE:[0-9]+]] - alwaysinline_fn(); -} - -#[inline(always)] -fn alwaysinline_fn() {} - -//CHECK: attributes #[[NOINLINE]] = {{{.*}} noinline {{.*}}} -//CHECK: attributes #[[INLINEHINT]] = {{{.*}} inlinehint {{.*}}} -//CHECK: attributes #[[ALWAYSINLINE]] = {{{.*}} alwaysinline {{.*}}} From 6d00f272fd193c15404616962e90be35cfc4fcc6 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 18 Sep 2026 14:57:31 +1000 Subject: [PATCH 22/76] Add a new `-Ztrack-diagnostics` test It covers a case that isn't currently covered: `-Ztrack-diagnostics` in combination with a `span_bug!` ICE. Notably, the "created at" line mentions `callbacks.rs`, which is the wrong location. This will be fixed in the next commit. --- tests/ui/track-diagnostics/track7.rs | 40 ++++++++++++++++++++++++ tests/ui/track-diagnostics/track7.stderr | 29 +++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/ui/track-diagnostics/track7.rs create mode 100644 tests/ui/track-diagnostics/track7.stderr diff --git a/tests/ui/track-diagnostics/track7.rs b/tests/ui/track-diagnostics/track7.rs new file mode 100644 index 0000000000000..f43c1e1a81c23 --- /dev/null +++ b/tests/ui/track-diagnostics/track7.rs @@ -0,0 +1,40 @@ +// This test checks that -Ztrack-diagnostics reports the correct source locations for an ICE +// triggered with `span_bug!`. +// +//@ compile-flags: -Zvalidate-mir -Ztrack-diagnostics +//@ rustc-env:RUST_BACKTRACE=0 +//@ failure-status: 101 +// +// Normalize the emitted location so this doesn't need +// updating everytime someone adds or removes a line. +//@ normalize-stderr: ".rs:\d+:\d+" -> ".rs:LL:CC" +//@ normalize-stderr: "note: rustc .+ running on .+" -> "note: rustc $$VERSION running on $$TARGET" +//@ normalize-stderr: "/rustc(?:-dev)?/[a-z0-9.]+/" -> "" +//@ normalize-stderr: "track7\[....\]" -> "track7[HASH]" +// The test becomes too flaky if we care about exact args. If `-Z ui-testing` +// from compiletest and `-Z track-diagnostics` from `// compile-flags` at the +// top of this file are present, then assume all args are present. +//@ normalize-stderr: "note: compiler flags: .*-Z ui-testing.*-Z track-diagnostics" -> "note: compiler flags: ... -Z ui-testing ... -Z track-diagnostics" + +#![feature(custom_mir, core_intrinsics)] +extern crate core; +use core::intrinsics::mir::*; + +fn bar(_x: i32) {} + +// Use of `mir!` here is just because it's an easy way to trigger a `span_bug!`. +#[custom_mir(dialect = "built")] +pub fn main() { + mir! { + let a: (i32, i32); + { + a = (1, 2); + Call(RET = bar(Move(a.0)), ReturnTo(retblock), UnwindContinue()) + //~^ ERROR broken MIR in + //~| ERROR encountered `Move` of a non-local, non-box place in `Call` terminator + } + retblock = { + Return() + } + } +} diff --git a/tests/ui/track-diagnostics/track7.stderr b/tests/ui/track-diagnostics/track7.stderr new file mode 100644 index 0000000000000..1b81276dd129b --- /dev/null +++ b/tests/ui/track-diagnostics/track7.stderr @@ -0,0 +1,29 @@ +error: internal compiler error: compiler/rustc_mir_transform/src/validate.rs:LL:CC: broken MIR in Item(DefId(0:6 ~ track7[HASH]::main)) (after pass LintAndRemoveUninhabited) at bb0[1]: + encountered `Move` of a non-local, non-box place in `Call` terminator: _0 = bar(move (_1.0: i32)) -> [return: bb1, unwind continue] + --> $DIR/track7.rs:LL:CC + | +LL | Call(RET = bar(Move(a.0)), ReturnTo(retblock), UnwindContinue()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: -Ztrack-diagnostics: created at compiler/rustc_interface/src/callbacks.rs:LL:CC + + +thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC: +broken MIR in Item(DefId(0:6 ~ track7[HASH]::main)) (after pass LintAndRemoveUninhabited) at bb0[1]: +encountered `Move` of a non-local, non-box place in `Call` terminator: _0 = bar(move (_1.0: i32)) -> [return: bb1, unwind continue] +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +error: the compiler unexpectedly panicked. This is a bug + +note: using internal features is not supported and expected to cause internal compiler errors when used incorrectly + +note: rustc $VERSION running on $TARGET + +note: compiler flags: ... -Z ui-testing ... -Z track-diagnostics + +query stack during panic: +#0 [mir_built] building MIR for `main` +#1 [has_ffi_unwind_calls] checking if `main` contains FFI-unwind calls +... and 3 other queries... use `env RUST_BACKTRACE=1` to see the full query stack +error: aborting due to 1 previous error + From 0e617d448a6b06792f642e636a7fc7f38c46eb76 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 17 Sep 2026 06:34:02 +1000 Subject: [PATCH 23/76] Adjust `bug!`/`span_bug!` emission PR #161873 moved these macros from `rustc_middle` to `rustc_span`. In doing so it made some undesirable changes. - The internal `emit_producing_nothing` became public. - It gave the macros different behaviour to function-based ICEs like `dcx.bug(..)` or `dcx.struct_bug(..).emit()`. - `ExplicitBug` is not used, so `report_ice` prints an extraneous, unintended "the compiler unexpectedly panicked" message. - It broke the interaction between `span_bug!` and `-Ztrack-diagnostics`. This commit adjusts things to avoid the undesirable changes. It required moving `ExplicitBug` to `rustc_span`. Setting `diag.emitted_at` fixed the `-Ztrack-diagnostics` problem, seen in the change from `callback.rs` to `validate.rs` in `track7.stderr`. `Box` output now occurs again in a few tests because the panic payload is `ExplicitBug` (which std doesn't know about) rather than `String`. That might be worth addressing in the future but it would require a different/additional mechanism and is beyond the scope of this PR. --- compiler/rustc_errors/src/diagnostic.rs | 9 +++-- compiler/rustc_errors/src/lib.rs | 5 +-- compiler/rustc_interface/src/callbacks.rs | 24 ++++++++---- compiler/rustc_span/src/macros.rs | 38 +++++++++++++++---- .../miri/tests/panic/mir-validation.stderr | 5 +-- tests/ui/intrinsics/not-overridden.stderr | 2 - ...tiple_definitions_attribute_merging.stderr | 4 +- .../proc_macro_generated_packed.stderr | 4 +- tests/ui/track-diagnostics/track7.stderr | 7 +--- 9 files changed, 60 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 5017f1228f2d0..dcaa81e37d2c9 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -93,10 +93,13 @@ pub struct DiagLocation { } impl DiagLocation { + pub fn from_location(loc: &'static panic::Location<'static>) -> Self { + DiagLocation { file: loc.file().into(), line: loc.line(), col: loc.column() } + } + #[track_caller] pub fn caller() -> Self { - let loc = panic::Location::caller(); - DiagLocation { file: loc.file().into(), line: loc.line(), col: loc.column() } + Self::from_location(panic::Location::caller()) } } @@ -1294,7 +1297,7 @@ impl<'a, G> Diag<'a, G> { } /// Most `emit` methods use this as a starting point. - pub fn emit_producing_nothing(mut self) { + fn emit_producing_nothing(mut self) { let diag = self.take_diag(); self.dcx.emit_diagnostic(diag); } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 9ca0344058d7f..b32c83e04d9c0 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -57,6 +57,7 @@ pub use rustc_macros::msg; use rustc_macros::{Decodable, Encodable}; pub use rustc_span::ErrorGuaranteed; pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker, catch_fatal_errors}; +pub use rustc_span::macros::ExplicitBug; use rustc_span::source_map::SourceMap; use rustc_span::{DUMMY_SP, Span}; use tracing::debug; @@ -256,10 +257,6 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s } } -/// Signifies that the compiler died with an explicit call to `.bug` -/// or `.span_bug` rather than a failed assertion, etc. -pub struct ExplicitBug; - /// Signifies that the compiler died due to a delayed bug rather than a failed /// assertion, etc. pub struct DelayedBugPanic; diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs index 0d8b565b4e384..2b9170c80de21 100644 --- a/compiler/rustc_interface/src/callbacks.rs +++ b/compiler/rustc_interface/src/callbacks.rs @@ -13,7 +13,7 @@ use std::fmt; use std::fmt::Arguments; use std::panic::Location; -use rustc_errors::DiagInner; +use rustc_errors::{DiagInner, DiagLocation, Level}; use rustc_middle::dep_graph::{QuerySideEffect, TaskDepsRef}; use rustc_middle::ty::tls; use rustc_span::{Span, Symbol}; @@ -86,16 +86,26 @@ fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> write!(f, ")") } -fn emit_bug_diagnostic(span: Option, args: Arguments<'_>, location: &Location<'_>) { +/// Returns true if it printed the diagnostic, which happens if a `tcx` is available. +fn emit_bug_diagnostic( + span: Option, + args: Arguments<'_>, + location: &'static Location<'static>, +) -> bool { tls::with_opt(move |tcx| { if let Some(tcx) = tcx { - let message = format!("{location}: {args}"); + let mut diag = DiagInner::new(Level::Bug, format!("{location}: {args}")); if let Some(span) = span { - tcx.dcx().struct_span_bug(span, message) - } else { - tcx.dcx().struct_bug(message) + diag.span = span.into(); } - .emit_producing_nothing(); + diag.emitted_at = DiagLocation::from_location(location); + // Emit the bug without aborting. We let `bug_impl` do the abort because it has + // `#[track_caller]` which gives a better location. (`#[track_caller]` doesn't work + // here because this function is called via a function pointer.) + tcx.dcx().emit_diagnostic(diag); + true + } else { + false } }) } diff --git a/compiler/rustc_span/src/macros.rs b/compiler/rustc_span/src/macros.rs index 9b042a2b416c5..db8b7ebbfa11f 100644 --- a/compiler/rustc_span/src/macros.rs +++ b/compiler/rustc_span/src/macros.rs @@ -1,10 +1,14 @@ use std::fmt; -use std::panic::Location; +use std::panic::{Location, panic_any}; use rustc_data_structures::AtomicRef; use crate::Span; +/// Signifies that the compiler died with an explicit call to `.bug` or `.span_bug` rather than a +/// failed assertion, etc. +pub struct ExplicitBug; + /// A macro for triggering an ICE. /// Calling `bug` instead of panicking will result in a nicer error message and should /// therefore be preferred over `panic`/`unreachable` or others. @@ -40,12 +44,32 @@ pub macro span_bug($span:expr, $($arg:tt)+){ #[cold] #[track_caller] -pub fn bug_impl(span: Option, args: fmt::Arguments<'_>, location: &Location<'_>) -> ! { - (*EMIT_BUG_DIAGNOSTIC)(span, args, location); - panic!("{args}") +pub fn bug_impl( + span: Option, + args: fmt::Arguments<'_>, + location: &'static Location<'static>, +) -> ! { + // Emit the bug without aborting. + let emitted = (*EMIT_BUG_DIAGNOSTIC)(span, args, location); + + if emitted { + // Panic with `ExplicitBug`, which tells `report_ice` that it's expected, e.g. originating + // from `bug!` or `dcx.emit_bug(..)`. + panic_any(ExplicitBug); + } else { + // Panic with just a string, which means it's unexpected. + panic_any(format!("{args}")); + } } -pub static EMIT_BUG_DIAGNOSTIC: AtomicRef, fmt::Arguments<'_>, &Location<'_>)> = - AtomicRef::new(&(default_emit_diagnostic as _)); +pub static EMIT_BUG_DIAGNOSTIC: AtomicRef< + fn(Option, fmt::Arguments<'_>, &'static Location<'static>) -> bool, +> = AtomicRef::new(&(default_emit_bug_diagnostic as _)); -fn default_emit_diagnostic(_: Option, _: fmt::Arguments<'_>, _: &Location<'_>) {} +fn default_emit_bug_diagnostic( + _: Option, + _args: fmt::Arguments<'_>, + _location: &'static Location<'static>, +) -> bool { + false +} diff --git a/src/tools/miri/tests/panic/mir-validation.stderr b/src/tools/miri/tests/panic/mir-validation.stderr index 115820510dd89..1d40c93d709e6 100644 --- a/src/tools/miri/tests/panic/mir-validation.stderr +++ b/src/tools/miri/tests/panic/mir-validation.stderr @@ -7,12 +7,9 @@ LL | *(tuple.0) = 1; thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC: -broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]: -place (*(_2.0: *mut i32)) has deref as a later projection (it is only permitted as the first projection) +Box stack backtrace: -error: the compiler unexpectedly panicked. This is a bug - diff --git a/tests/ui/intrinsics/not-overridden.stderr b/tests/ui/intrinsics/not-overridden.stderr index ae5586b2f0a85..45c5c37318b89 100644 --- a/tests/ui/intrinsics/not-overridden.stderr +++ b/tests/ui/intrinsics/not-overridden.stderr @@ -5,8 +5,6 @@ LL | unsafe { const_deallocate(std::ptr::null_mut(), 0, 0) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: the compiler unexpectedly panicked. This is a bug - query stack during panic: end of query stack error: aborting due to 1 previous error diff --git a/tests/ui/resolve/multiple_definitions_attribute_merging.stderr b/tests/ui/resolve/multiple_definitions_attribute_merging.stderr index 63bdcfddf3ca2..b8b33e3417bf7 100644 --- a/tests/ui/resolve/multiple_definitions_attribute_merging.stderr +++ b/tests/ui/resolve/multiple_definitions_attribute_merging.stderr @@ -17,9 +17,7 @@ LL | struct Dealigned(u8, T); | ^ -builtin derive created an unaligned reference -error: the compiler unexpectedly panicked. This is a bug - +Box query stack during panic: #0 [mir_built] building MIR for `::eq` #1 [check_unsafety] unsafety-checking `::eq` diff --git a/tests/ui/resolve/proc_macro_generated_packed.stderr b/tests/ui/resolve/proc_macro_generated_packed.stderr index 3e63abb4b9e6b..d8e160d0c6a03 100644 --- a/tests/ui/resolve/proc_macro_generated_packed.stderr +++ b/tests/ui/resolve/proc_macro_generated_packed.stderr @@ -8,9 +8,7 @@ LL | struct Dealigned(u8, T); | ^ -builtin derive created an unaligned reference -error: the compiler unexpectedly panicked. This is a bug - +Box query stack during panic: #0 [mir_built] building MIR for `::eq` #1 [check_unsafety] unsafety-checking `::eq` diff --git a/tests/ui/track-diagnostics/track7.stderr b/tests/ui/track-diagnostics/track7.stderr index 1b81276dd129b..61a615f782b8e 100644 --- a/tests/ui/track-diagnostics/track7.stderr +++ b/tests/ui/track-diagnostics/track7.stderr @@ -5,16 +5,13 @@ error: internal compiler error: compiler/rustc_mir_transform/src/validate.rs:LL: LL | Call(RET = bar(Move(a.0)), ReturnTo(retblock), UnwindContinue()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: -Ztrack-diagnostics: created at compiler/rustc_interface/src/callbacks.rs:LL:CC + = note: -Ztrack-diagnostics: created at compiler/rustc_mir_transform/src/validate.rs:LL:CC thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC: -broken MIR in Item(DefId(0:6 ~ track7[HASH]::main)) (after pass LintAndRemoveUninhabited) at bb0[1]: -encountered `Move` of a non-local, non-box place in `Call` terminator: _0 = bar(move (_1.0: i32)) -> [return: bb1, unwind continue] +Box note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -error: the compiler unexpectedly panicked. This is a bug - note: using internal features is not supported and expected to cause internal compiler errors when used incorrectly note: rustc $VERSION running on $TARGET From bf91751cda156af266c0e731089783970c8de368 Mon Sep 17 00:00:00 2001 From: Flakebi Date: Thu, 3 Sep 2026 09:21:39 +0200 Subject: [PATCH 24/76] Pre-commit amdgpu gpu-kernel ABI test --- tests/codegen-llvm/amdgpu-abi/struct-abi.rs | 133 ++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/codegen-llvm/amdgpu-abi/struct-abi.rs diff --git a/tests/codegen-llvm/amdgpu-abi/struct-abi.rs b/tests/codegen-llvm/amdgpu-abi/struct-abi.rs new file mode 100644 index 0000000000000..bf51cbfa7f7a0 --- /dev/null +++ b/tests/codegen-llvm/amdgpu-abi/struct-abi.rs @@ -0,0 +1,133 @@ +//@ add-minicore +//@ compile-flags: --crate-type=rlib --target=amdgcn-amd-amdhsa -Ctarget-cpu=gfx900 -Copt-level=3 +//@ needs-llvm-components: amdgpu +#![feature(no_core, abi_gpu_kernel)] +#![no_core] +#![allow(improper_gpu_kernel_arg)] + +extern crate minicore; +use minicore::num::Complex; + +// Tests from llvm-project/clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl + +#[repr(C)] +pub struct SingleElementStructArg { + i: i32, +} + +#[repr(C)] +pub struct NestedSingleElementStructArg { + i: SingleElementStructArg, +} + +#[repr(C)] +pub struct StructArg { + i1: i32, + f: f32, + i2: i32, +} + +#[repr(C)] +pub struct StructPaddingArg { + i1: i8, + f: i64, +} + +#[repr(C)] +pub struct StructOfArraysArg { + i1: [i32; 2], + f1: f32, + i2: [i32; 4], + f2: [f32; 3], + i3: i32, +} + +#[repr(C)] +pub struct StructOfStructsArg { + i1: i32, + f1: f32, + s1: StructArg, + i2: i32, +} + +#[repr(C)] +pub union U { + b1: i32, + b2: f32, +} + +#[repr(C)] +pub struct SingleArrayElementStructArg { + i: [i32; 4], +} + +#[repr(C)] +pub struct SingleStructElementStructArgInner { + i: i32, + b: i64, +} + +#[repr(C)] +pub struct SingleStructElementStructArg { + s: SingleStructElementStructArgInner, +} + +#[repr(C)] +pub struct DifferentSizeTypePair { + l: i64, + i: i32, +} + +// CHECK: define amdgpu_kernel void @kernel_single_element_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(4) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_single_element_struct_arg(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_nested_single_element_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(4) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_nested_single_element_struct_arg( + _: NestedSingleElementStructArg, +) { +} + +// CHECK: define amdgpu_kernel void @kernel_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(12) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_struct_arg(_: StructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_struct_padding_arg(i8 noundef {{%.+}}, i64 noundef {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_struct_padding_arg(_: StructPaddingArg) {} + +// CHECK: define amdgpu_kernel void @kernel_struct_of_arrays_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(44) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_struct_of_arrays_arg(_: StructOfArraysArg) {} + +// CHECK: define amdgpu_kernel void @kernel_struct_of_structs_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(24) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_struct_of_structs_arg(_: StructOfStructsArg) {} + +// CHECK: define amdgpu_kernel void @test_kernel_union_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(4) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn test_kernel_union_arg(_: U) {} + +// CHECK: define amdgpu_kernel void @kernel_single_array_element_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(16) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_single_array_element_struct_arg(_: SingleArrayElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_single_struct_element_struct_arg(i32 noundef {{%.+}}, i64 noundef {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_single_struct_element_struct_arg( + _: SingleStructElementStructArg, +) { +} + +// CHECK: define amdgpu_kernel void @kernel_different_size_type_pair_arg(i64 noundef {{%.+}}, i32 noundef {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_different_size_type_pair_arg(_: DifferentSizeTypePair) {} + +// CHECK: define amdgpu_kernel void @kernel_complex(float noundef {{%.+}}, float noundef {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_complex(_: Complex) {} + +// CHECK: define amdgpu_kernel void @kernel_slice(ptr noalias nofree noundef nonnull readonly align 4 captures(none) {{%.+}}, i64 noundef range(i64 0, 2305843009213693952) {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_slice(_: &[u32]) {} From c4982542b6f702e25d3cebb55db4c2e29ea51662 Mon Sep 17 00:00:00 2001 From: Flakebi Date: Tue, 15 Sep 2026 10:33:14 +0200 Subject: [PATCH 25/76] Properly implement the gpu-kernel ABI for amdgpu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support to pass structs, arrays and vectors to amdgpu kernels. Scalars and vectors are taken by value, aggregates are passed by byref pointers. Structs containing a single scalar/vector are handled like a scalar. Judging from clang tests, nvptx seems to do somewhat the same, just using byval instead of byref: https://github.com/llvm/llvm-project/blob/3a8affeef4da19d39191aac316e189eca3214a8c/clang/test/CodeGenCUDA/kernel-args.cu I tested a couple of the lit test signatures on real hardware and it seems to work fine. Given the relatively simple implementation, I hope this amount of testing is enough (the C calling convention seems like a worse fit for Rust’s current ABI code, it’s still giving me headaches). --- compiler/rustc_abi/src/lib.rs | 4 + compiler/rustc_target/src/callconv/amdgpu.rs | 82 ++++++++++++++---- tests/codegen-llvm/amdgpu-abi/struct-abi.rs | 88 ++++++++++++++++---- 3 files changed, 139 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index b056fdc73d40b..79c0309c60aa3 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -1772,6 +1772,10 @@ pub struct AddressSpace(pub u32); impl AddressSpace { /// LLVM's `0` address space. pub const ZERO: Self = AddressSpace(0); + /// The address space for constant memory on nvptx and amdgpu. + /// This address space is used e.g. for kernel arguments that are constant throughout the + /// execution. + pub const GPU_CONSTANT: Self = AddressSpace(4); /// The address space for workgroup memory on nvptx and amdgpu. /// See e.g. the `gpu_launch_sized_workgroup_mem` intrinsic for details. pub const GPU_WORKGROUP: Self = AddressSpace(3); diff --git a/compiler/rustc_target/src/callconv/amdgpu.rs b/compiler/rustc_target/src/callconv/amdgpu.rs index 98ab3ce8eb746..7a9eeaba19c96 100644 --- a/compiler/rustc_target/src/callconv/amdgpu.rs +++ b/compiler/rustc_target/src/callconv/amdgpu.rs @@ -1,25 +1,60 @@ -use rustc_abi::{HasDataLayout, TyAbiInterface}; +use rustc_abi::{ + AddressSpace, BackendRepr, CanonAbi, HasDataLayout, Reg, RegKind, TyAbiInterface, TyAndLayout, +}; -use crate::callconv::{ArgAbi, FnAbi}; +use crate::callconv::{FnAbi, Uniform}; -fn classify_ret<'a, Ty, C>(_cx: &C, ret: &mut ArgAbi<'a, Ty>) -where - Ty: TyAbiInterface<'a, C> + Copy, - C: HasDataLayout, -{ - ret.extend_integer_width_to(32); -} +// For reference, see llvm-project/clang/lib/CodeGen/Targets/AMDGPU.cpp -fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +/// If the given type is a (potentially nested) struct containing a single scalar, return +/// a `Uniform` for the contained, single element. +fn single_element_struct_to_reg<'a, Ty, C>(cx: &C, ty: TyAndLayout<'a, Ty>) -> Option where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { - arg.make_indirect(); - return; + assert!(ty.is_aggregate(), "Only handles aggregate types"); + if ty.layout.fields.count() != 1 { + return None; + } + let field = ty.field(cx, 0); + match field.backend_repr { + BackendRepr::SimdScalableVector { .. } => panic!("scalable vectors are unsupported"), + BackendRepr::Scalar(_) => { + // Check that the size is the same as the size for ty, so no extra padding + let size = field.layout.size.bytes(); + if ty.layout.size.bytes() != size { + return None; + } + + // clang passes the inner type directly, we emulate it with fitting integer types + match size { + 1 => Some(Uniform::new(Reg::i8(), field.layout.size)), + 2 => Some(Uniform::new(Reg::i16(), field.layout.size)), + 4 => Some(Uniform::new(Reg::i32(), field.layout.size)), + 8 => Some(Uniform::new(Reg::i64(), field.layout.size)), + 16 => Some(Uniform::new(Reg::i128(), field.layout.size)), + s => panic!("Unhandled scalar of size {s} in amdgpu gpu-kernel ABI"), + } + } + BackendRepr::SimdVector { element, .. } => { + // Check that the size is the same as the size for ty, so no extra padding + let size = field.layout.size.bytes(); + if ty.layout.size.bytes() != size { + return None; + } + + // clang passes the inner type directly, we emulate it with a vector of the same type. + // The size is rounded up to the size of the complete type (including alignment). + let reg = Reg { + kind: RegKind::Vector { hint_vector_elem: element.primitive() }, + size: field.layout.size, + }; + Some(Uniform::new(reg, field.layout.size)) + } + BackendRepr::Memory { .. } => single_element_struct_to_reg(cx, field), + BackendRepr::ScalarPair { .. } => None, } - arg.extend_integer_width_to(32); } pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) @@ -27,14 +62,25 @@ where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - if !fn_abi.ret.is_ignore() { - classify_ret(cx, &mut fn_abi.ret); - } + // Kernels cannot return values, so do not handle return types + // Try to fill first registers with values and pass by_ref pointers for later indirect arguments for arg in fn_abi.args.iter_mut() { if arg.is_ignore() { continue; } - classify_arg(cx, arg); + if fn_abi.conv == CanonAbi::GpuKernel { + if arg.layout.is_aggregate() { + if let Some(uniform) = single_element_struct_to_reg(cx, arg.layout) { + // Single element structs are passed directly as the inner type + arg.cast_to(uniform); + } else { + // All other aggregates are passed as by_ref pointer in the constant address space + arg.pass_amdgpu_kernel_arg(Some(AddressSpace::GPU_CONSTANT)); + } + } + } else { + // FIXME: C ABI is not yet implemented + } } } diff --git a/tests/codegen-llvm/amdgpu-abi/struct-abi.rs b/tests/codegen-llvm/amdgpu-abi/struct-abi.rs index bf51cbfa7f7a0..bc83f6510a6fe 100644 --- a/tests/codegen-llvm/amdgpu-abi/struct-abi.rs +++ b/tests/codegen-llvm/amdgpu-abi/struct-abi.rs @@ -1,7 +1,7 @@ //@ add-minicore //@ compile-flags: --crate-type=rlib --target=amdgcn-amd-amdhsa -Ctarget-cpu=gfx900 -Copt-level=3 //@ needs-llvm-components: amdgpu -#![feature(no_core, abi_gpu_kernel)] +#![feature(no_core, abi_gpu_kernel, repr_simd)] #![no_core] #![allow(improper_gpu_kernel_arg)] @@ -10,14 +10,32 @@ use minicore::num::Complex; // Tests from llvm-project/clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl +#[repr(simd)] +pub struct I8X2([i8; 2]); + +#[repr(simd)] +pub struct I16X2([i16; 2]); + +#[repr(simd)] +pub struct I16X3([i16; 3]); + +#[repr(simd)] +pub struct I16X4([i16; 4]); + +#[repr(simd)] +pub struct I32X3([i32; 3]); + +#[repr(simd)] +pub struct I32X4([i32; 4]); + #[repr(C)] -pub struct SingleElementStructArg { - i: i32, +pub struct SingleElementStructArg { + i: T, } #[repr(C)] pub struct NestedSingleElementStructArg { - i: SingleElementStructArg, + i: SingleElementStructArg, } #[repr(C)] @@ -78,56 +96,92 @@ pub struct DifferentSizeTypePair { i: i32, } -// CHECK: define amdgpu_kernel void @kernel_single_element_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(4) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_single_element_struct_arg(i32 %0) #[no_mangle] -pub extern "gpu-kernel" fn kernel_single_element_struct_arg(_: SingleElementStructArg) {} +pub extern "gpu-kernel" fn kernel_single_element_struct_arg(_: SingleElementStructArg) {} -// CHECK: define amdgpu_kernel void @kernel_nested_single_element_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(4) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_nested_single_element_struct_arg(i32 %0) #[no_mangle] pub extern "gpu-kernel" fn kernel_nested_single_element_struct_arg( _: NestedSingleElementStructArg, ) { } -// CHECK: define amdgpu_kernel void @kernel_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(12) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_struct_arg(ptr addrspace(4) noalias nofree noundef readnone byref([12 x i8]) align 4 captures(none) dereferenceable(12) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_struct_arg(_: StructArg) {} -// CHECK: define amdgpu_kernel void @kernel_struct_padding_arg(i8 noundef {{%.+}}, i64 noundef {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_struct_padding_arg(ptr addrspace(4) noalias nofree noundef readnone byref([16 x i8]) align 8 captures(none) dereferenceable(16) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_struct_padding_arg(_: StructPaddingArg) {} -// CHECK: define amdgpu_kernel void @kernel_struct_of_arrays_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(44) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_struct_of_arrays_arg(ptr addrspace(4) noalias nofree noundef readnone byref([44 x i8]) align 4 captures(none) dereferenceable(44) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_struct_of_arrays_arg(_: StructOfArraysArg) {} -// CHECK: define amdgpu_kernel void @kernel_struct_of_structs_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(24) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_struct_of_structs_arg(ptr addrspace(4) noalias nofree noundef readnone byref([24 x i8]) align 4 captures(none) dereferenceable(24) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_struct_of_structs_arg(_: StructOfStructsArg) {} -// CHECK: define amdgpu_kernel void @test_kernel_union_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(4) {{%.+}}) +// CHECK: define amdgpu_kernel void @test_kernel_union_arg(ptr addrspace(4) noalias nofree noundef readnone byref([4 x i8]) align 4 captures(none) dereferenceable(4) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn test_kernel_union_arg(_: U) {} -// CHECK: define amdgpu_kernel void @kernel_single_array_element_struct_arg(ptr noalias nofree noundef readnone align 4 captures(none) dead_on_return dereferenceable(16) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_single_array_element_struct_arg(ptr addrspace(4) noalias nofree noundef readnone byref([16 x i8]) align 4 captures(none) dereferenceable(16) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_single_array_element_struct_arg(_: SingleArrayElementStructArg) {} -// CHECK: define amdgpu_kernel void @kernel_single_struct_element_struct_arg(i32 noundef {{%.+}}, i64 noundef {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_single_struct_element_struct_arg(ptr addrspace(4) noalias nofree noundef readnone byref([16 x i8]) align 8 captures(none) dereferenceable(16) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_single_struct_element_struct_arg( _: SingleStructElementStructArg, ) { } -// CHECK: define amdgpu_kernel void @kernel_different_size_type_pair_arg(i64 noundef {{%.+}}, i32 noundef {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_different_size_type_pair_arg(ptr addrspace(4) noalias nofree noundef readnone byref([16 x i8]) align 8 captures(none) dereferenceable(16) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_different_size_type_pair_arg(_: DifferentSizeTypePair) {} -// CHECK: define amdgpu_kernel void @kernel_complex(float noundef {{%.+}}, float noundef {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_complex(ptr addrspace(4) noalias nofree noundef readnone byref([8 x i8]) align 4 captures(none) dereferenceable(8) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_complex(_: Complex) {} -// CHECK: define amdgpu_kernel void @kernel_slice(ptr noalias nofree noundef nonnull readonly align 4 captures(none) {{%.+}}, i64 noundef range(i64 0, 2305843009213693952) {{%.+}}) +// CHECK: define amdgpu_kernel void @kernel_slice(ptr addrspace(4) noalias nofree noundef readnone byref([16 x i8]) align 8 captures(none) dereferenceable(16) {{%.+}}) #[no_mangle] pub extern "gpu-kernel" fn kernel_slice(_: &[u32]) {} + +// CHECK: define amdgpu_kernel void @kernel_i64(i64 noundef {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i64(_: i64) {} + +// CHECK: define amdgpu_kernel void @kernel_i64_struct(i64 {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i64_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i128_struct(i128 {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i128_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i8x2_struct(<2 x i8> {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i8x2_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i16x2_struct(<2 x i16> {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i16x2_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i16x3_struct(<4 x i16> {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i16x3_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i16x4_struct(<4 x i16> {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i16x4_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i32x3_struct(<4 x i32> {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i32x3_struct(_: SingleElementStructArg) {} + +// CHECK: define amdgpu_kernel void @kernel_i32x4_struct(<4 x i32> {{%.+}}) +#[no_mangle] +pub extern "gpu-kernel" fn kernel_i32x4_struct(_: SingleElementStructArg) {} From 653076c76960316dc6a88a88c020bb07072a35fe Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Fri, 18 Sep 2026 09:15:10 +0200 Subject: [PATCH 26/76] yeet AliasConstKind::opt_def_id --- .../rustc_hir_analysis/src/check/check.rs | 20 ++++++-- .../src/hir_ty_lowering/mod.rs | 48 +++++++++---------- compiler/rustc_middle/src/thir.rs | 2 +- .../rustc_middle/src/ty/abstract_const.rs | 11 +++-- .../src/thir/pattern/check_match.rs | 3 +- .../src/thir/pattern/const_to_pat.rs | 2 +- .../src/unstable/convert/stable/ty.rs | 10 ++-- compiler/rustc_type_ir/src/const_kind.rs | 10 ---- .../mgca/inherent-alias-default.rs | 15 ++++++ tests/ui/thir-print/str-patterns.stdout | 4 +- 10 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 tests/ui/const-generics/mgca/inherent-alias-default.rs diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 705bb780a3ecf..52b69e6050a32 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -775,9 +775,23 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), if has_default { // need to store default and type of default let ct = tcx.const_param_default(param.def_id).skip_binder(); - if let ty::ConstKind::Alias(_, alias_const) = ct.kind() - && let Some(def_id) = alias_const.kind.opt_def_id() - { + if let ty::ConstKind::Alias(_, alias_const) = ct.kind() { + let def_id = match alias_const.kind { + ty::AliasConstKind::Projection { def_id } => def_id, + ty::AliasConstKind::InherentSelf { def_id } => { + // NOTE: typically, InherentSelf is illegal to pass to type_of, + // because the generic args are incorrect (type_of expects impl-form + // arguments). However, we are just checking ensure_ok().type_of(), + // we are not instantiating the result, so it's OK here. + def_id + } + ty::AliasConstKind::InherentImpl { .. } => span_bug!( + tcx.def_span(param.def_id), + "const_param_default should return an unnormalized constant, which should always be InherentSelf, not InherentImpl" + ), + ty::AliasConstKind::Free { def_id } => def_id, + ty::AliasConstKind::Anon { def_id } => def_id, + }; tcx.ensure_ok().type_of(def_id); } } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index e89caa6aeff8c..ecfe5d3c2c7c2 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -1480,9 +1480,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { )? { TypeRelativePath::AssocItem(alias_term) => { let alias_ct = alias_term.expect_ct(); - if let Some(def_id) = alias_ct.kind.opt_def_id() { - self.check_const_item_in_type_system(def_id, span)?; - } + self.check_const_item_in_type_system(alias_ct.kind, span)?; let ct = Const::new_alias(tcx, ty::IsRigid::No, alias_ct); let ct = self.check_param_uses_if_mcg(ct, span, false); Ok(ct) @@ -1948,13 +1946,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { item_segment, ty::AssocTag::Const, )?; - self.check_const_item_in_type_system(item_def_id, span)?; - let alias_const = ty::AliasConst::new( - tcx, - ty::AliasConstKind::Projection { def_id: item_def_id }, - item_args, - ); - Ok(Const::new_alias(tcx, ty::IsRigid::No, alias_const)) + let kind = ty::AliasConstKind::Projection { def_id: item_def_id }; + self.check_const_item_in_type_system(kind, span)?; + let alias = ty::AliasConst::new(tcx, kind, item_args); + Ok(Const::new_alias(tcx, ty::IsRigid::No, alias)) } /// Lower a [resolved][hir::QPath::Resolved] (type-level) associated item path. @@ -2879,7 +2874,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self.lower_const_param(def_id, hir_id) } Res::Def(DefKind::Const, did) => { - if let Err(guar) = self.check_const_item_in_type_system(did, span) { + let kind = ty::AliasConstKind::Free { def_id: did }; + if let Err(guar) = self.check_const_item_in_type_system(kind, span) { return Const::new_error(self.tcx(), guar); } @@ -2888,11 +2884,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let _ = self .prohibit_generic_args(leading_segments.iter(), GenericsArgsErrExtend::None); let args = self.lower_generic_args_of_path_segment(span, did, segment); - ty::Const::new_alias( - tcx, - ty::IsRigid::No, - ty::AliasConst::new(tcx, ty::AliasConstKind::Free { def_id: did }, args), - ) + let alias = ty::AliasConst::new(tcx, kind, args); + ty::Const::new_alias(tcx, ty::IsRigid::No, alias) } Res::Def(kind @ DefKind::Ctor(ctor_of, CtorKind::Const), did) => { assert_eq!(opt_self_ty, None); @@ -3126,18 +3119,27 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { /// `def_id` is a const item used in the type system. Checks if that's OK. fn check_const_item_in_type_system( &self, - def_id: DefId, + alias_const: ty::AliasConstKind<'tcx>, span: Span, ) -> Result<(), ErrorGuaranteed> { let tcx = self.tcx(); - if tcx.features().generic_const_args() || tcx.is_direct_const(def_id) { + if tcx.features().generic_const_args() || alias_const.is_direct_const(tcx) { Ok(()) } else { let mut err = self .dcx() .struct_span_err(span, "use of `const` in the type system not marked as direct"); - if let Some(local_def_id) = def_id.as_local() { - if let Some(body_id) = tcx.hir_node_by_def_id(local_def_id).body_id() { + let hir_node = match alias_const { + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::InherentSelf { def_id } + | ty::AliasConstKind::InherentImpl { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => { + def_id.as_local().map(|id| tcx.hir_node_by_def_id(id)) + } + }; + if let Some(hir_node) = hir_node { + if let Some(body_id) = hir_node.body_id() { let body_span = tcx.hir_body(body_id).value.span; err.multipart_suggestion( @@ -3148,10 +3150,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ], Applicability::MaybeIncorrect, ); - } else if let DefKind::AssocConst = tcx.def_kind(def_id) - && let DefKind::Trait = tcx.def_kind(tcx.parent(def_id)) - { - let node = tcx.hir_node_by_def_id(local_def_id).expect_trait_item(); + } else if let ty::AliasConstKind::Projection { .. } = alias_const { + let node = hir_node.expect_trait_item(); let sp = node.span.shrink_to_lo(); err.span_suggestion_verbose( sp, diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index fe7b1bf493051..b20dfe68d98e2 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -661,7 +661,7 @@ pub struct PatExtra<'tcx> { /// /// This is used by some diagnostics for non-exhaustive matches, to map /// the pattern node back to the `DefId` of its original constant. - pub expanded_const: Option, + pub expanded_const: Option>, /// User-written types that must be preserved into MIR so that they can be /// checked. diff --git a/compiler/rustc_middle/src/ty/abstract_const.rs b/compiler/rustc_middle/src/ty/abstract_const.rs index 2853c43ae079d..2227841923514 100644 --- a/compiler/rustc_middle/src/ty/abstract_const.rs +++ b/compiler/rustc_middle/src/ty/abstract_const.rs @@ -52,9 +52,14 @@ impl<'tcx> TyCtxt<'tcx> { } fn fold_const(&mut self, c: Const<'tcx>) -> Const<'tcx> { let ct = match c.kind() { - ty::ConstKind::Alias(_, alias_const) - if let Some(def_id) = alias_const.kind.opt_def_id() => - { + ty::ConstKind::Alias(_, alias_const) => { + let def_id = match alias_const.kind { + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::InherentSelf { def_id } + | ty::AliasConstKind::InherentImpl { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => def_id, + }; match self.tcx.thir_abstract_const(def_id) { Err(e) => ty::Const::new_error(self.tcx, e), Ok(Some(bac)) => { diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index befe4d67253a3..3414ee751585c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -1229,8 +1229,7 @@ fn is_const_pat_that_looks_like_binding<'tcx>(tcx: TyCtxt<'tcx>, pat: &Pat<'tcx> // The pattern must be a named constant, and the name that appears in // the pattern's source text must resemble a plain identifier without any // `::` namespace separators or other non-identifier characters. - if let Some(def_id) = try { pat.extra.as_deref()?.expanded_const? } - && tcx.def_kind(def_id) == DefKind::Const + if let ty::AliasConstKind::Free { def_id } = pat.extra.as_deref()?.expanded_const? && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(pat.span) && snippet.chars().all(|c| c.is_alphanumeric() || c == '_') { diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 55eef6006f278..e24ef5ea5b52d 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -224,7 +224,7 @@ impl<'tcx> ConstToPat<'tcx> { // Mark the pattern to indicate that it is the result of lowering a named // constant. This is used for diagnostics. - thir_pat.extra.get_or_insert_default().expanded_const = alias_const.kind.opt_def_id(); + thir_pat.extra.get_or_insert_default().expanded_const = Some(alias_const.kind); thir_pat } diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index ad1aa1b47132a..c3eebccb6b762 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -557,9 +557,13 @@ impl<'tcx> Stable<'tcx> for ty::Const<'tcx> { } ty::ConstKind::Param(param) => crate::ty::TyConstKind::Param(param.stable(tables, cx)), ty::ConstKind::Alias(_, alias_const) => { - let Some(def_id) = alias_const.kind.opt_def_id() else { - // FIXME: implement (both AliasTy and AliasConst will be needing this soon) - panic!("non-defid alias consts are not supported by rustc_public at the moment") + // rustc_public must change its API once we introduce a variant without a def_id. + let def_id = match alias_const.kind { + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::InherentSelf { def_id } + | ty::AliasConstKind::InherentImpl { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => def_id, }; crate::ty::TyConstKind::Unevaluated( tables.const_def(def_id), diff --git a/compiler/rustc_type_ir/src/const_kind.rs b/compiler/rustc_type_ir/src/const_kind.rs index dd0578610a2a0..f66322e034814 100644 --- a/compiler/rustc_type_ir/src/const_kind.rs +++ b/compiler/rustc_type_ir/src/const_kind.rs @@ -160,16 +160,6 @@ impl AliasConstKind { AliasConstKind::Anon { def_id } => interner.def_span(def_id.into()), } } - - pub fn opt_def_id(self) -> Option { - match self { - AliasConstKind::Projection { def_id } => Some(def_id.into()), - AliasConstKind::InherentSelf { def_id } => Some(def_id.into()), - AliasConstKind::InherentImpl { def_id } => Some(def_id.into()), - AliasConstKind::Free { def_id } => Some(def_id.into()), - AliasConstKind::Anon { def_id } => Some(def_id.into()), - } - } } rustc_index::newtype_index! { diff --git a/tests/ui/const-generics/mgca/inherent-alias-default.rs b/tests/ui/const-generics/mgca/inherent-alias-default.rs new file mode 100644 index 0000000000000..9ba6d1d15e856 --- /dev/null +++ b/tests/ui/const-generics/mgca/inherent-alias-default.rs @@ -0,0 +1,15 @@ +//@ check-pass +//! rustc_hir_analysis::check_item_type does type_of() on the default value. This is wonky, because +//! the generic args are in Self format at that point, not in impl format, so the result can't be +//! used with the Self-format args. However, it does not instantiate the result, it just does +//! ensure_ok(). This test just makes sure that codepath is hit in tests. +#![feature(min_generic_const_args, inherent_associated_types)] + +struct Struct(T1, T2, T3); +impl Struct { + const INHERENT: usize = core::direct_const_arg!(2); +} + +struct WithDefault::INHERENT) }>; + +fn main() {} diff --git a/tests/ui/thir-print/str-patterns.stdout b/tests/ui/thir-print/str-patterns.stdout index da1f86b8fc591..61bcbaef5029a 100644 --- a/tests/ui/thir-print/str-patterns.stdout +++ b/tests/ui/thir-print/str-patterns.stdout @@ -46,7 +46,9 @@ Thir { extra: Some( PatExtra { expanded_const: Some( - DefId(0:4 ~ str_patterns[fc71]::CONSTANT), + Free { + def_id: DefId(0:4 ~ str_patterns[fc71]::CONSTANT), + }, ), ascriptions: [], }, From 44d6e57165985b97a890d670a281deb15d008bf3 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 10:47:41 +0200 Subject: [PATCH 27/76] sembr src/offload/installation.md --- src/doc/rustc-dev-guide/src/offload/installation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index db7b69715f078..baf689f458077 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -21,7 +21,8 @@ cd rust If you would rather reuse an existing clang than build one, drop `--enable-clang` and pass `--enable-llvm-offload-clang-dir=` -instead. It should match the (major version of the) LLVM in `src/llvm-project`. +instead. +It should match the (major version of the) LLVM in `src/llvm-project`. Afterwards you can build rustc using: ```console From ae341a25a0a6cc5129978cd391bc02c824275512 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 10:48:43 +0200 Subject: [PATCH 28/76] sembr src/test-implementation.md --- .../src/test-implementation.md | 86 ++++++++++--------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/test-implementation.md b/src/doc/rustc-dev-guide/src/test-implementation.md index 707ba040609e0..a3739ad2fdae6 100644 --- a/src/doc/rustc-dev-guide/src/test-implementation.md +++ b/src/doc/rustc-dev-guide/src/test-implementation.md @@ -2,8 +2,8 @@ -Many Rust programmers rely on a built-in attribute called `#[test]`. All -you have to do is mark a function and include some asserts like so: +Many Rust programmers rely on a built-in attribute called `#[test]`. +All you have to do is mark a function and include some asserts like so: ```rust,ignore @@ -14,9 +14,9 @@ fn my_test() { ``` When this program is compiled using `rustc --test` or `cargo test`, it will -produce an executable that can run this, and any other test function. This -method of testing allows tests to live alongside code in an organic way. You -can even put tests inside private modules: +produce an executable that can run this, and any other test function. +This method of testing allows tests to live alongside code in an organic way. +You can even put tests inside private modules: ```rust,ignore mod my_priv_mod { @@ -30,22 +30,23 @@ mod my_priv_mod { ``` Private items can thus be easily tested without worrying about how to expose -them to any sort of external testing apparatus. This is key to the -ergonomics of testing in Rust. Semantically, however, it's rather odd. +them to any sort of external testing apparatus. +This is key to the ergonomics of testing in Rust. +Semantically, however, it's rather odd. How does any sort of `main` function invoke these tests if they're not visible? What exactly is `rustc --test` doing? `#[test]` is implemented as a syntactic transformation inside the compiler's -[`rustc_ast`][rustc_ast]. Essentially, it's a fancy [`macro`] that -rewrites the crate in 3 steps: +[`rustc_ast`][rustc_ast]. +Essentially, it's a fancy [`macro`] that rewrites the crate in 3 steps: ## Step 1: Re-Exporting As mentioned earlier, tests can exist inside private modules, so we need a -way of exposing them to the main function, without breaking any existing -code. To that end, [`rustc_ast`][rustc_ast] will create local modules called -`__test_reexports` that recursively reexport tests. This expansion translates -the above example into: +way of exposing them to the main function, without breaking any existing code. +To that end, [`rustc_ast`][rustc_ast] will create local modules called +`__test_reexports` that recursively reexport tests. +This expansion translates the above example into: ```rust,ignore mod my_priv_mod { @@ -61,24 +62,27 @@ mod my_priv_mod { } ``` -Now, our test can be accessed as -`my_priv_mod::__test_reexports::test_priv_func`. For deeper module -structures, `__test_reexports` will reexport modules that contain tests, so a -test at `a::b::my_test` becomes -`a::__test_reexports::b::__test_reexports::my_test`. While this process seems -pretty safe, what happens if there is an existing `__test_reexports` module? +Now, our test can be accessed as `my_priv_mod::__test_reexports::test_priv_func`. +For deeper module structures, `__test_reexports` will reexport modules that contain tests, so a +test at `a::b::my_test` becomes `a::__test_reexports::b::__test_reexports::my_test`. +While this process seems pretty safe, +what happens if there is an existing `__test_reexports` module? The answer: nothing. To explain, we need to understand how Rust's [Abstract Syntax Tree][ast] -represents [identifiers][Ident]. The name of every function, variable, module, -etc. is not stored as a string, but rather as an opaque [Symbol][Symbol] which -is essentially an ID number for each identifier. The compiler keeps a separate +represents [identifiers][Ident]. +The name of every function, variable, module, +etc. +is not stored as a string, but rather as an opaque [Symbol][Symbol] which +is essentially an ID number for each identifier. +The compiler keeps a separate hashtable that allows us to recover the human-readable name of a Symbol when -necessary (such as when printing a syntax error). When the compiler generates -the `__test_reexports` module, it generates a new [Symbol][Symbol] for the +necessary (such as when printing a syntax error). +When the compiler generates the `__test_reexports` module, +it generates a new [Symbol][Symbol] for the identifier, so while the compiler-generated `__test_reexports` may share a name -with your hand-written one, it will not share a [Symbol][Symbol]. This -technique prevents name collision during code generation and is the foundation +with your hand-written one, it will not share a [Symbol][Symbol]. +This technique prevents name collision during code generation and is the foundation of Rust's [`macro`] hygiene. ## Step 2: Harness generation @@ -96,20 +100,20 @@ pub fn main() { Here `path::to::test1` is a constant of type [`test::TestDescAndFn`][tdaf]. -While this transformation is simple, it gives us a lot of insight into how -tests are actually run. The tests are aggregated into an array and passed to -a test runner called `test_main_env_args`. We'll come back to exactly what -[`TestDescAndFn`][tdaf] is, but for now, the key takeaway is that there is a crate -called [`test`][test] that is part of Rust core, that implements all of the -runtime for testing. [`test`][test]'s interface is unstable, so the only stable way +While this transformation is simple, it gives us a lot of insight into how tests are actually run. +The tests are aggregated into an array and passed to a test runner called `test_main_env_args`. +We'll come back to exactly what [`TestDescAndFn`][tdaf] is, +but for now, the key takeaway is that there is a crate +called [`test`][test] that is part of Rust core, that implements all of the runtime for testing. +[`test`][test]'s interface is unstable, so the only stable way to interact with it is through the `#[test]` macro. ## Step 3: Test object generation If you've written tests in Rust before, you may be familiar with some of the -optional attributes available on test functions. For example, a test can be -annotated with `#[should_panic]` if we expect the test to cause a panic. It -looks something like this: +optional attributes available on test functions. +For example, a test can be annotated with `#[should_panic]` if we expect the test to cause a panic. +It looks something like this: ```rust,ignore #[test] @@ -120,12 +124,12 @@ fn foo() { ``` This means our tests are more than just simple functions, they have -configuration information as well. `test` encodes this configuration data into -a `struct` called [`TestDesc`]. For each test function in a crate, -[`rustc_ast`][rustc_ast] will parse its attributes and generate a [`TestDesc`] -instance. It then combines the [`TestDesc`] and test function into the -predictably named [`TestDescAndFn`][tdaf] `struct`, that [`test_main_env_args`] -operates on. +configuration information as well. +`test` encodes this configuration data into a `struct` called [`TestDesc`]. +For each test function in a crate, +[`rustc_ast`][rustc_ast] will parse its attributes and generate a [`TestDesc`] instance. +It then combines the [`TestDesc`] and test function into the +predictably named [`TestDescAndFn`][tdaf] `struct`, that [`test_main_env_args`] operates on. For a given test, the generated [`TestDescAndFn`][tdaf] instance looks like so: ```rust,ignore From 9327b11234231777f8bcdc6d672f1fae4b648510 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 10:52:27 +0200 Subject: [PATCH 29/76] redundant --- src/doc/rustc-dev-guide/src/test-implementation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/test-implementation.md b/src/doc/rustc-dev-guide/src/test-implementation.md index a3739ad2fdae6..77146b8a71d33 100644 --- a/src/doc/rustc-dev-guide/src/test-implementation.md +++ b/src/doc/rustc-dev-guide/src/test-implementation.md @@ -73,15 +73,15 @@ To explain, we need to understand how Rust's [Abstract Syntax Tree][ast] represents [identifiers][Ident]. The name of every function, variable, module, etc. -is not stored as a string, but rather as an opaque [Symbol][Symbol] which +is not stored as a string, but rather as an opaque [Symbol] which is essentially an ID number for each identifier. The compiler keeps a separate hashtable that allows us to recover the human-readable name of a Symbol when necessary (such as when printing a syntax error). When the compiler generates the `__test_reexports` module, -it generates a new [Symbol][Symbol] for the +it generates a new [Symbol] for the identifier, so while the compiler-generated `__test_reexports` may share a name -with your hand-written one, it will not share a [Symbol][Symbol]. +with your hand-written one, it will not share a [Symbol]. This technique prevents name collision during code generation and is the foundation of Rust's [`macro`] hygiene. From 8a9db74b79ecb82312c230b9813bd9afd38e9b6d Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 10:56:06 +0200 Subject: [PATCH 30/76] whitespace --- src/doc/rustc-dev-guide/src/test-implementation.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/test-implementation.md b/src/doc/rustc-dev-guide/src/test-implementation.md index 77146b8a71d33..cf2c6fb1dc9e1 100644 --- a/src/doc/rustc-dev-guide/src/test-implementation.md +++ b/src/doc/rustc-dev-guide/src/test-implementation.md @@ -1,7 +1,5 @@ # The `#[test]` attribute - - Many Rust programmers rely on a built-in attribute called `#[test]`. All you have to do is mark a function and include some asserts like so: From ec4984195ef7c7b6989aadc276fc99620c7762a9 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 11:19:23 +0200 Subject: [PATCH 31/76] reflow --- .../rustc-dev-guide/src/test-implementation.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/test-implementation.md b/src/doc/rustc-dev-guide/src/test-implementation.md index cf2c6fb1dc9e1..5df7d8d304d22 100644 --- a/src/doc/rustc-dev-guide/src/test-implementation.md +++ b/src/doc/rustc-dev-guide/src/test-implementation.md @@ -69,16 +69,13 @@ The answer: nothing. To explain, we need to understand how Rust's [Abstract Syntax Tree][ast] represents [identifiers][Ident]. -The name of every function, variable, module, -etc. -is not stored as a string, but rather as an opaque [Symbol] which -is essentially an ID number for each identifier. -The compiler keeps a separate -hashtable that allows us to recover the human-readable name of a Symbol when -necessary (such as when printing a syntax error). +The name of every function, variable, module, etc. is not stored as a string, +but rather as an opaque [Symbol] which is essentially an ID number for each identifier. +The compiler keeps a separate hashtable that allows us to recover +the human-readable name of a Symbol when necessary (such as when printing a syntax error). When the compiler generates the `__test_reexports` module, -it generates a new [Symbol] for the -identifier, so while the compiler-generated `__test_reexports` may share a name +it generates a new [Symbol] for the identifier, +so while the compiler-generated `__test_reexports` may share a name with your hand-written one, it will not share a [Symbol]. This technique prevents name collision during code generation and is the foundation of Rust's [`macro`] hygiene. From 9e7d4b22edf5c5fa303673c52f2985f7ea823748 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 14:54:42 +1000 Subject: [PATCH 32/76] Don't double-allocate `OwnerInfo` `into_owner_info` arena-allocates the created `OwnerInfo`. `ItemLowerer::with_lctx` calls `into_owner_info` and then re-arena-allocates the returned `OwnerInfo` (the reference, not the entire struct). This commit removes the latter. --- compiler/rustc_ast_lowering/src/item.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index edf184f568b22..24d5a80c59fab 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -62,8 +62,7 @@ impl<'hir> ItemLowerer<'_, 'hir> { let item = f(&mut lctx); - let info = lctx.curr_owner.into_owner_info(self.tcx, item); - hir::MaybeOwner::Owner(lctx.arena.alloc(info)) + hir::MaybeOwner::Owner(lctx.curr_owner.into_owner_info(self.tcx, item)) } #[instrument(level = "debug", skip(self, c))] From 9a03326dc02ee524716005615061b5494093bc93 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 14:59:45 +1000 Subject: [PATCH 33/76] Convert some `&mut self` to `&self` in the lowerer --- compiler/rustc_ast_lowering/src/item.rs | 12 ++++++------ compiler/rustc_ast_lowering/src/lib.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 24d5a80c59fab..7fe5a089fe155 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -54,7 +54,7 @@ fn add_ty_alias_where_clause( impl<'hir> ItemLowerer<'_, 'hir> { fn with_lctx( - &mut self, + &self, owner: NodeId, f: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::OwnerNode<'hir>, ) -> hir::MaybeOwner<'hir> { @@ -66,7 +66,7 @@ impl<'hir> ItemLowerer<'_, 'hir> { } #[instrument(level = "debug", skip(self, c))] - pub(super) fn lower_crate(&mut self, c: &Crate) -> hir::MaybeOwner<'hir> { + pub(super) fn lower_crate(&self, c: &Crate) -> hir::MaybeOwner<'hir> { self.with_lctx(CRATE_NODE_ID, |lctx| { debug_assert_eq!(lctx.curr_owner.owner_id(), CRATE_OWNER_ID); let module = lctx.lower_mod(&c.items, &c.spans); @@ -76,19 +76,19 @@ impl<'hir> ItemLowerer<'_, 'hir> { } #[instrument(level = "debug", skip(self))] - pub(super) fn lower_item(&mut self, item: &Item) -> hir::MaybeOwner<'hir> { + pub(super) fn lower_item(&self, item: &Item) -> hir::MaybeOwner<'hir> { self.with_lctx(item.id, |lctx| hir::OwnerNode::Item(lctx.lower_item(item))) } - pub(super) fn lower_trait_item(&mut self, item: &AssocItem) -> hir::MaybeOwner<'hir> { + pub(super) fn lower_trait_item(&self, item: &AssocItem) -> hir::MaybeOwner<'hir> { self.with_lctx(item.id, |lctx| hir::OwnerNode::TraitItem(lctx.lower_trait_item(item))) } - pub(super) fn lower_impl_item(&mut self, item: &AssocItem) -> hir::MaybeOwner<'hir> { + pub(super) fn lower_impl_item(&self, item: &AssocItem) -> hir::MaybeOwner<'hir> { self.with_lctx(item.id, |lctx| hir::OwnerNode::ImplItem(lctx.lower_impl_item(item))) } - pub(super) fn lower_foreign_item(&mut self, item: &ForeignItem) -> hir::MaybeOwner<'hir> { + pub(super) fn lower_foreign_item(&self, item: &ForeignItem) -> hir::MaybeOwner<'hir> { self.with_lctx(item.id, |lctx| hir::OwnerNode::ForeignItem(lctx.lower_foreign_item(item))) } } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 19c37f4a76065..eb64d5027cdba 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -783,7 +783,7 @@ fn lower_to_hir(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::MaybeOwner<'_> { return fallback_to_ancestor(tcx.local_parent(def_id)); }; - let mut item_lowerer = item::ItemLowerer { tcx, resolver: &*resolver }; + let item_lowerer = item::ItemLowerer { tcx, resolver: &*resolver }; let item = match &node { // The item existed in the AST. @@ -982,7 +982,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } #[instrument(level = "trace", skip(self))] - fn lower_res(&mut self, res: Res) -> Res { + fn lower_res(&self, res: Res) -> Res { let res: Result = res.apply_id(|id| { let owner = self.curr_owner.owner_id(); let local_id = @@ -999,11 +999,11 @@ impl<'hir> LoweringContext<'_, 'hir> { res.unwrap_or(Res::Err) } - fn expect_full_res(&mut self, id: NodeId) -> Res { + fn expect_full_res(&self, id: NodeId) -> Res { self.get_partial_res(id).map_or(Res::Err, |pr| pr.expect_full_res()) } - fn lower_import_res(&mut self, id: NodeId, span: Span) -> PerNS> { + fn lower_import_res(&self, id: NodeId, span: Span) -> PerNS> { debug_assert_eq!(id, self.curr_owner.owner.id); let per_ns = self.curr_owner.owner.import_res.map(|res| res.map(|res| self.lower_res(res))); if per_ns.is_empty() { @@ -3058,7 +3058,7 @@ impl<'hir> LoweringContext<'_, 'hir> { })) } - fn lower_unsafe_source(&mut self, u: UnsafeSource) -> hir::UnsafeSource { + fn lower_unsafe_source(&self, u: UnsafeSource) -> hir::UnsafeSource { match u { CompilerGenerated => hir::UnsafeSource::CompilerGenerated, UserProvided => hir::UnsafeSource::UserProvided, @@ -3066,7 +3066,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } fn lower_trait_bound_modifiers( - &mut self, + &self, modifiers: TraitBoundModifiers, ) -> hir::TraitBoundModifiers { let constness = match modifiers.constness { From 990c292e4178cff8526a50aa47221936a5440feb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 15:07:48 +1000 Subject: [PATCH 34/76] Reduce the scope of a local --- compiler/rustc_ast_lowering/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index eb64d5027cdba..087182e43f95c 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1647,8 +1647,8 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_array_length_to_const_arg(length), ), TyKind::TraitObject(bounds, kind) => { - let mut lifetime_bound = None; let (bounds, lifetime_bound) = self.with_dyn_type_scope(true, |this| { + let mut lifetime_bound = None; let bounds = this.arena.alloc_from_iter(bounds.iter().filter_map(|bound| match bound { // We can safely ignore constness here since AST validation @@ -1681,9 +1681,7 @@ impl<'hir> LoweringContext<'_, 'hir> { None } })); - let lifetime_bound = - lifetime_bound.unwrap_or_else(|| this.elided_dyn_bound(t.span)); - (bounds, lifetime_bound) + (bounds, lifetime_bound.unwrap_or_else(|| this.elided_dyn_bound(t.span))) }); hir::TyKind::TraitObject(bounds, TaggedRef::new(lifetime_bound, *kind)) } From 9b817c55d6e7cd0bc232952bbc4661b833a9b601 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 15:08:45 +1000 Subject: [PATCH 35/76] Rename `LoweringContext::current_item` As `LoweringContext::current_item_span`, because it *is* a span. --- compiler/rustc_ast_lowering/src/expr.rs | 4 ++-- compiler/rustc_ast_lowering/src/lib.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 1c1b9a247f7a2..c3bc86a352644 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1018,7 +1018,7 @@ impl<'hir> LoweringContext<'_, 'hir> { expr.span, hir::ExprKind::Err(self.dcx().emit_err(AwaitOnlyInAsyncFnAndBlocks { await_kw_span, - item_span: self.current_item, + item_span: self.current_item_span, })), ); return hir::ExprKind::Block( @@ -1712,7 +1712,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } Some(hir::CoroutineKind::Coroutine(_)) => false, None => { - let suggestion = self.current_item.map(|s| s.shrink_to_lo()); + let suggestion = self.current_item_span.map(|s| s.shrink_to_lo()); self.dcx().emit_err(YieldInClosure { span, suggestion }); self.coroutine_kind = Some(hir::CoroutineKind::Coroutine(Movability::Movable)); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 087182e43f95c..b9d6f478c9ac0 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -296,7 +296,7 @@ struct LoweringContext<'a, 'hir> { /// Used to get the current `fn`'s def span to point to when using `await` /// outside of an `async fn`. - current_item: Option, + current_item_span: Option, try_block_scope: TryBlockScope, loop_scope: Option, @@ -366,7 +366,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { is_in_dyn_type: false, coroutine_kind: None, task_context: None, - current_item: None, + current_item_span: None, move_expr_bindings: Vec::new(), lowering_move_expr_initializer: false, @@ -1154,8 +1154,8 @@ impl<'hir> LoweringContext<'_, 'hir> { } fn with_new_scopes(&mut self, scope_span: Span, f: impl FnOnce(&mut Self) -> T) -> T { - let current_item = self.current_item; - self.current_item = Some(scope_span); + let current_item_span = self.current_item_span; + self.current_item_span = Some(scope_span); let was_in_loop_condition = self.is_in_loop_condition; self.is_in_loop_condition = false; @@ -1172,7 +1172,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.is_in_loop_condition = was_in_loop_condition; - self.current_item = current_item; + self.current_item_span = current_item_span; ret } From 3ee39bdf7cc213a7e4e7ff70d91948302f485dfc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 15:09:32 +1000 Subject: [PATCH 36/76] Fix an inconsistent comment --- compiler/rustc_ast_lowering/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index b9d6f478c9ac0..609644804b203 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -824,7 +824,7 @@ enum GenericArgsMode { ParenSugar, /// Allow RTN, don't allow paren sugar. ReturnTypeNotation, - // Error if parenthesized generics or RTN are encountered. + /// Error if parenthesized generics or RTN are encountered. Err, /// Silence errors when lowering generics. Only used with `Res::Err`. Silence, From 6e7d014afa5e089a0ffab69c738ca635bfbfd963 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 21:50:32 +1000 Subject: [PATCH 37/76] Inline and remove `lower_delim_args` It's trivial and has a single call site. --- compiler/rustc_ast_lowering/src/item.rs | 2 +- compiler/rustc_ast_lowering/src/lib.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 7fe5a089fe155..72f72d22fb13a 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -543,7 +543,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } ItemKind::MacroDef(ident, MacroDef { body, macro_rules, eii_declaration: _ }) => { let ident = self.lower_ident(*ident); - let body = Box::new(self.lower_delim_args(body)); + let body = body.clone(); let def_id = self.curr_owner.owner.def_id; let def_kind = self.tcx.def_kind(def_id); let DefKind::Macro(macro_kinds) = def_kind else { diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 609644804b203..16d098d1c4d75 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1261,10 +1261,6 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_delim_args(&self, args: &DelimArgs) -> DelimArgs { - args.clone() - } - /// Lower an associated item constraint. #[instrument(level = "debug", skip_all)] fn lower_assoc_item_constraint( From f1b023a91b8455d214035c657b35adf77dc31451 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 11:21:16 +0200 Subject: [PATCH 38/76] sembr src/solve/sharing-crates-with-rust-analyzer.md --- .../sharing-crates-with-rust-analyzer.md | 83 +++++++++---------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md b/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md index 2b06f5b414c1b..83d3a0ce0ab3a 100644 --- a/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md +++ b/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md @@ -77,7 +77,8 @@ Among its essential responsibilities: instantiates the shared IR, - it provides the context required by the solver (e.g., querying [lang items][ir require_lang_item], enumerating [all blanket impls for a trait][ir for_each_blanket_impl]); -- and it must implement [`IrPrint`][ir irprint] for formatting and tracing. +- and it must implement [`IrPrint`][ir irprint] for formatting and tracing. + In practice, these `IrPrint` impls simply route to existing formatting logic inside rustc or rust-analyzer. @@ -91,7 +92,8 @@ rather than rustc queries. Another notable item in `rustc_type_ir` is the [`inherent` module][ir inherent]. This module provides *forward definitions* of inherent methods—expressed as traits—corresponding to -methods that exist on compiler-specific types such as `Ty` or `GenericArg`. +methods that exist on compiler-specific types such as `Ty` or `GenericArg`. + These definitions allow the generic crates (such as `rustc_next_trait_solver`) to call methods that are implemented differently in rustc and rust-analyzer. @@ -152,8 +154,8 @@ This infrastructure is used by the external fuzzing project: - [`trait GenericTypeVisitable`][generictypevisitable] These traits are used heavily in `rustc_type_ir`, their associated macros -primarily exist to reduce the amount of boilerplate otherwise required to -implement `Lift`, `TypeFoldable`, `TypeVisitable` and `GenericTypeVisitable`. +primarily exist to reduce the amount of boilerplate otherwise required to implement `Lift`, +`TypeFoldable`, `TypeVisitable` and `GenericTypeVisitable`. ### `trait TypeVisitable` and `TypeVisitable_Generic` [type-visitable-trait-macro]: #type-visitable-trait-macro @@ -163,8 +165,7 @@ which in turn will transfer control to `TypeVisitor`, this can be [seen in detail here][rustc_typevisitable]. While ostensibly similar due to their names, `TypeVisitable_Generic` and -[`GenericTypeVisitable`][generictypevisitable] they implement two different -visiting systems. +[`GenericTypeVisitable`][generictypevisitable] they implement two different visiting systems. - `TypeVisitable_Generic` means: derive the ordinary `TypeVisitable` trait generically over an `Interner`. @@ -175,50 +176,49 @@ visiting systems. [typevisitable_generic]: #typevisitable_generic It visits the value's fields in declaration order, delegating each field to that -field's own `TypeVisitable` implementation. The traversal can stop early if -the visitor returns a residual result. +field's own `TypeVisitable` implementation. +The traversal can stop early if the visitor returns a residual result. Use `#[type_visitable(ignore)]` to ignore a field; it will not be part of the -traversal and will not need to implement `TypeVisitable`. This should only -be used when the field does not need to be traversed. +traversal and will not need to implement `TypeVisitable`. +This should only be used when the field does not need to be traversed. ### `trait TypeFoldable` and `TypeFoldable_Generic` [type-foldable-trait-macro]: #type-foldable-trait-macro -The trait is implemented by things that need to embed types. This concept is -discussed in detail [here](../ty-fold.md) and can be +The trait is implemented by things that need to embed types. +This concept is discussed in detail [here](../ty-fold.md) and can be [followed in the source][rustc_typefoldable]. -`TypeFoldable_Generic` derives `rustc_type_ir::TypeFoldable` for a struct or -enum. +`TypeFoldable_Generic` derives `rustc_type_ir::TypeFoldable` for a struct or enum. -It consumes a value and reconstructs the same struct or enum variant after -folding its fields. It generates both fallible and infallible folding methods. +It consumes a value and reconstructs the same struct or enum variant after folding its fields. +It generates both fallible and infallible folding methods. -Use `#[type_foldable(identity)]` for a field whose value must be preserved -unchanged. The macro moves that field directly into the reconstructed value -instead of passing it to the folder. Its type therefore does not need to -implement `TypeFoldable`. +Use `#[type_foldable(identity)]` for a field whose value must be preserved unchanged. +The macro moves that field directly into the reconstructed value +instead of passing it to the folder. +Its type therefore does not need to implement `TypeFoldable`. For an enum, the generated match contains one reconstruction arm per variant. ### `trait Lift` and `Lift_Generic` [lift-trait-macro]: #lift-trait-macro -The trait has a method `lift_to_interner(...)`. As the name suggests, it should -'lift' something to the interner. [See here](../memory.md) to read more about -the interner [and here for the source][rustc_lift]. +The trait has a method `lift_to_interner(...)`. +As the name suggests, it should 'lift' something to the interner. +[See here](../memory.md) to read more about the interner [and here for the source][rustc_lift]. -The macro `Lift_Generic` derives `Lift` for a struct or enum, with three -non-obvious considerations: +The macro `Lift_Generic` derives `Lift` for a struct or enum, with three non-obvious considerations: 1. The generic parameters `I` and `J` are reserved for `I: Interner` and `J` being the interner it is being lifted to. -2. `PhantomData` is handled automatically, creating a new `PhantomData`. But it - _has_ to be used in the fully unqualified form -- you cannot use +2. `PhantomData` is handled automatically, creating a new `PhantomData`. + But it _has_ to be used in the fully unqualified form -- you cannot use `std::marker::PhantomData` directly in the field. 3. The bounds are deliberately written as associated type bounds on the `Interner` - trait rather than as `where` clauses on `LiftInto`. Given only `I: LiftInto`, + trait rather than as `where` clauses on `LiftInto`. + Given only `I: LiftInto`, Rust can then treat bounds such as the following as implied: ```rust @@ -228,15 +228,14 @@ I::Const: Lift This allows `Lift_Generic` to emit the bound `I: LiftInto` while still calling `lift_to_interner` on fields of type `I::Ty`, `I::Const`, and the other -declared associated types. It also guarantees that each call produces the -destination field type expected after the derive rewrites `I::Assoc` to -`J::Assoc`. +declared associated types. +It also guarantees that each call produces the +destination field type expected after the derive rewrites `I::Assoc` to `J::Assoc`. Without `declare_lift_into!`, the derive would need to generate a separate bound -for every interner-associated type used by every field. If a new `Interner` -associated type is expected to work with `Lift_Generic`, it needs an appropriate -`Lift` implementation and normally needs to be included in the -`declare_lift_into!` invocation. +for every interner-associated type used by every field. +If a new `Interner` associated type is expected to work with `Lift_Generic`, it needs an appropriate +`Lift` implementation and normally needs to be included in the `declare_lift_into!` invocation. If you want to ignore a field, such as a primitive like a `u32` which can't be lifted you can skip the field with `#[lift(ignore)]`. @@ -245,20 +244,20 @@ lifted you can skip the field with `#[lift(ignore)]`. [generictypevisitable]: #generictypevisitable This a separate more general traversal trait purely used by `rust-analyzer`. -The visitor type is a parameter of the trait rather than a parameter of the -method, and visiting neither returns a result nor supports short-circuiting. +The visitor type is a parameter of the trait rather than a parameter of the method, +and visiting neither returns a result nor supports short-circuiting. -As such a struct or enum can derive both `TypeVisitable_Generic` and -`GenericTypeVisitable` +As such a struct or enum can derive both `TypeVisitable_Generic` and `GenericTypeVisitable` -There is intentionally no ignore attribute. The traversal must visit every -field. This is a soundness requirement for rust-analyzer's use of the traversal +There is intentionally no ignore attribute. +The traversal must visit every field. +This is a soundness requirement for rust-analyzer's use of the traversal when tracing and garbage-collecting interned types. ## Long-term plans for supporting rust-analyzer In general, we aim to support rust-analyzer just as well as rustc in these shared crates—provided -doing so does not substantially harm rustc's performance or maintainability. +doing so does not substantially harm rustc's performance or maintainability. (e.g., [#145377][pr 145377], [#146111][pr 146111], [#146182][pr 146182] and [#147723][pr 147723]) Shared crates that require nightly-only features must guard such code behind a `nightly` feature From 11b373edc96b4bc0f4cf2e1bd692bffbd5ac4dc4 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 2 Sep 2026 07:13:08 +1000 Subject: [PATCH 39/76] Eliminate `ItemLowerer` It's just a thin wrapper around `tcx` and `resolver`. The `lower_*` methods all have a single call site and can be inlined, and `with_lctx` can just be a local fn within `lower_to_hir`. This requires increasing the visibility of some `LoweringContext::lower_*` methods that are now called outside of `item.rs`. --- compiler/rustc_ast_lowering/src/item.rs | 65 +++---------------------- compiler/rustc_ast_lowering/src/lib.rs | 39 ++++++++++++--- 2 files changed, 39 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 72f72d22fb13a..1073706499919 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -5,11 +5,8 @@ use rustc_errors::{E0570, ErrorGuaranteed, struct_span_code_err}; use rustc_hir::attrs::{AttributeKind, EiiImplResolution}; use rustc_hir::def::{DefKind, PerNS, Res}; use rustc_hir::{ - self as hir, CRATE_OWNER_ID, HirId, ImplItemImplKind, LifetimeSource, PredicateOrigin, Target, - find_attr, + self as hir, HirId, ImplItemImplKind, LifetimeSource, PredicateOrigin, Target, find_attr, }; -use rustc_middle::middle::resolve::ResolverAstLowering; -use rustc_middle::ty::TyCtxt; use rustc_middle::ty::data_structures::IndexMap; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::edit_distance::find_best_match_for_name; @@ -28,14 +25,9 @@ use super::{ }; use crate::diagnostics::{ConstComptimeFn, ResolvingRestrictionKind, RestrictionAncestorOnly}; -pub(super) struct ItemLowerer<'a, 'hir> { - pub(super) tcx: TyCtxt<'hir>, - pub(super) resolver: &'a ResolverAstLowering<'hir>, -} - -/// When we have a ty alias we *may* have two where clauses. To give the best diagnostics, we set the span -/// to the where clause that is preferred, if it exists. Otherwise, it sets the span to the other where -/// clause if it exists. +/// When we have a ty alias we *may* have two where clauses. To give the best diagnostics, we set +/// the span to the where clause that is preferred, if it exists. Otherwise, it sets the span to +/// the other where clause if it exists. fn add_ty_alias_where_clause( generics: &mut ast::Generics, after_where_clause: &ast::WhereClause, @@ -52,47 +44,6 @@ fn add_ty_alias_where_clause( if before.0 || !after.0 { before } else { after }; } -impl<'hir> ItemLowerer<'_, 'hir> { - fn with_lctx( - &self, - owner: NodeId, - f: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::OwnerNode<'hir>, - ) -> hir::MaybeOwner<'hir> { - let mut lctx = LoweringContext::new(self.tcx, self.resolver, owner); - - let item = f(&mut lctx); - - hir::MaybeOwner::Owner(lctx.curr_owner.into_owner_info(self.tcx, item)) - } - - #[instrument(level = "debug", skip(self, c))] - pub(super) fn lower_crate(&self, c: &Crate) -> hir::MaybeOwner<'hir> { - self.with_lctx(CRATE_NODE_ID, |lctx| { - debug_assert_eq!(lctx.curr_owner.owner_id(), CRATE_OWNER_ID); - let module = lctx.lower_mod(&c.items, &c.spans); - lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs, c.spans.inner_span, Target::Crate); - hir::OwnerNode::Crate(module) - }) - } - - #[instrument(level = "debug", skip(self))] - pub(super) fn lower_item(&self, item: &Item) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::Item(lctx.lower_item(item))) - } - - pub(super) fn lower_trait_item(&self, item: &AssocItem) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::TraitItem(lctx.lower_trait_item(item))) - } - - pub(super) fn lower_impl_item(&self, item: &AssocItem) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::ImplItem(lctx.lower_impl_item(item))) - } - - pub(super) fn lower_foreign_item(&self, item: &ForeignItem) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::ForeignItem(lctx.lower_foreign_item(item))) - } -} - impl<'hir> LoweringContext<'_, 'hir> { pub(super) fn lower_mod( &mut self, @@ -202,7 +153,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_item(&mut self, i: &Item) -> &'hir hir::Item<'hir> { + pub(super) fn lower_item(&mut self, i: &Item) -> &'hir hir::Item<'hir> { let owner_id = self.curr_owner.owner_id(); let hir_id: HirId = owner_id.into(); let vis_span = self.lower_span(i.vis.span); @@ -729,7 +680,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_foreign_item(&mut self, i: &ForeignItem) -> &'hir hir::ForeignItem<'hir> { + pub(super) fn lower_foreign_item(&mut self, i: &ForeignItem) -> &'hir hir::ForeignItem<'hir> { let owner_id = self.curr_owner.owner_id(); let hir_id: HirId = owner_id.into(); let attrs = @@ -910,7 +861,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_trait_item(&mut self, i: &AssocItem) -> &'hir hir::TraitItem<'hir> { + pub(super) fn lower_trait_item(&mut self, i: &AssocItem) -> &'hir hir::TraitItem<'hir> { let trait_item_def_id = self.curr_owner.owner_id(); let hir_id: HirId = trait_item_def_id.into(); let attrs = self.lower_attrs( @@ -1159,7 +1110,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ident } - fn lower_impl_item(&mut self, i: &AssocItem) -> &'hir hir::ImplItem<'hir> { + pub(super) fn lower_impl_item(&mut self, i: &AssocItem) -> &'hir hir::ImplItem<'hir> { let owner_id = self.curr_owner.owner_id(); let hir_id: HirId = owner_id.into(); let parent_id = self.tcx.local_parent(owner_id.def_id); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 16d098d1c4d75..410823919c523 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -60,8 +60,9 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId, LocalDefIdMap}; use rustc_hir::definitions::PerParentDisambiguatorState; use rustc_hir::lints::DelayedLint; use rustc_hir::{ - self as hir, AngleBrackets, ConstArg, GenericArg, HirId, ItemLocalMap, LifetimeSource, - LifetimeSyntax, MissingLifetimeKind, ParamName, Target, TraitCandidate, find_attr, + self as hir, AngleBrackets, CRATE_OWNER_ID, ConstArg, GenericArg, HirId, ItemLocalMap, + LifetimeSource, LifetimeSyntax, MissingLifetimeKind, ParamName, Target, TraitCandidate, + find_attr, }; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_macros::extension; @@ -783,15 +784,37 @@ fn lower_to_hir(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::MaybeOwner<'_> { return fallback_to_ancestor(tcx.local_parent(def_id)); }; - let item_lowerer = item::ItemLowerer { tcx, resolver: &*resolver }; + fn with_lctx<'hir>( + tcx: TyCtxt<'hir>, + resolver: &ResolverAstLowering<'hir>, + owner: NodeId, + f: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::OwnerNode<'hir>, + ) -> hir::MaybeOwner<'hir> { + let mut lctx = LoweringContext::new(tcx, resolver, owner); + let item = f(&mut lctx); + hir::MaybeOwner::Owner(lctx.curr_owner.into_owner_info(tcx, item)) + } let item = match &node { // The item existed in the AST. - AstOwner::Crate(c) => item_lowerer.lower_crate(&c), - AstOwner::Item(item) => item_lowerer.lower_item(&item), - AstOwner::TraitItem(item) => item_lowerer.lower_trait_item(&item), - AstOwner::ImplItem(item) => item_lowerer.lower_impl_item(&item), - AstOwner::ForeignItem(item) => item_lowerer.lower_foreign_item(&item), + AstOwner::Crate(c) => with_lctx(tcx, &*resolver, CRATE_NODE_ID, |lctx| { + debug_assert_eq!(lctx.curr_owner.owner_id(), CRATE_OWNER_ID); + let module = lctx.lower_mod(&c.items, &c.spans); + lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs, c.spans.inner_span, Target::Crate); + hir::OwnerNode::Crate(module) + }), + AstOwner::Item(item) => { + with_lctx(tcx, &*resolver, item.id, |lctx| hir::OwnerNode::Item(lctx.lower_item(item))) + } + AstOwner::TraitItem(item) => with_lctx(tcx, &*resolver, item.id, |lctx| { + hir::OwnerNode::TraitItem(lctx.lower_trait_item(item)) + }), + AstOwner::ImplItem(item) => with_lctx(tcx, &*resolver, item.id, |lctx| { + hir::OwnerNode::ImplItem(lctx.lower_impl_item(item)) + }), + AstOwner::ForeignItem(item) => with_lctx(tcx, &*resolver, item.id, |lctx| { + hir::OwnerNode::ForeignItem(lctx.lower_foreign_item(item)) + }), AstOwner::NestedUseTree(owner_id) => fallback_to_ancestor(*owner_id), // The item existed in the AST, but is not a HIR owner. // Fetch the correct information from its parent. From d3f82464bc0f20835a14d6cdd7c8b3cdcc5cc17c Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 11:29:35 +0200 Subject: [PATCH 40/76] whitespace --- .../src/solve/sharing-crates-with-rust-analyzer.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md b/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md index 83d3a0ce0ab3a..19db5016eb073 100644 --- a/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md +++ b/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md @@ -59,7 +59,7 @@ solver. `rustc_next_trait_solver` is intended to depend only on the abstract interfaces defined in `rustc_type_ir`. To support this, the type-system traits in `rustc_type_ir` must expose every interface the solver -requires—for example, [creating a new inference type variable][ir new_infer] +requires—for example, [creating a new inference type variable][ir new_infer] ([rustc][rustc new_infer], [rust-analyzer][r-a new_infer]). For items that do not need compiler-specific representations, `rustc_type_ir` defines them directly as structs or enums parameterized over these traits—for example, [`TraitRef`][ir tr]. @@ -78,7 +78,7 @@ Among its essential responsibilities: - it provides the context required by the solver (e.g., querying [lang items][ir require_lang_item], enumerating [all blanket impls for a trait][ir for_each_blanket_impl]); - and it must implement [`IrPrint`][ir irprint] for formatting and tracing. - + In practice, these `IrPrint` impls simply route to existing formatting logic inside rustc or rust-analyzer. @@ -153,7 +153,7 @@ This infrastructure is used by the external fuzzing project: - [`trait Lift` and `Lift_Generic`][lift-trait-macro] - [`trait GenericTypeVisitable`][generictypevisitable] -These traits are used heavily in `rustc_type_ir`, their associated macros +These traits are used heavily in `rustc_type_ir`, their associated macros primarily exist to reduce the amount of boilerplate otherwise required to implement `Lift`, `TypeFoldable`, `TypeVisitable` and `GenericTypeVisitable`. @@ -264,8 +264,8 @@ Shared crates that require nightly-only features must guard such code behind a ` flag, since rust-analyzer is built with the stable toolchain. Looking forward, we plan to uplift more shared logic into `rustc_type_ir`. -There are still duplicated implementations between rustc and rust-analyzer—such as `ObligationCtxt` -([rustc][rustc oblctxt], [rust-analyzer][r-a oblctxt]) and type coercion logic +There are still duplicated implementations between rustc and rust-analyzer—such as `ObligationCtxt` +([rustc][rustc oblctxt], [rust-analyzer][r-a oblctxt]) and type coercion logic ([rustc][rustc coerce], [rust-analyzer][r-a coerce])—that we would like to unify over time. [rustc-auto-publish]: https://github.com/rust-analyzer/rustc-auto-publish @@ -302,5 +302,5 @@ There are still duplicated implementations between rustc and rust-analyzer—suc [rustc coerce]: https://github.com/rust-lang/rust/blob/63b1db05801271e400954e41b8600a3cf1482363/compiler/rustc_hir_typeck/src/coercion.rs [r-a coerce]: https://github.com/rust-lang/rust-analyzer/blob/34f47d9298c478c12c6c4c0348771d1b05706e09/crates/hir-ty/src/infer/coerce.rs [rustc_lift]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/lift.rs#L18 -[rustc_typevisitable]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/visit.rs#L62 +[rustc_typevisitable]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/visit.rs#L62 [rustc_typefoldable]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/fold.rs#L71 From 1b9a6084b151161c3b0023d35de83ba053610e53 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 11:31:47 +0200 Subject: [PATCH 41/76] sembr src/tests/ecosystem.md --- src/doc/rustc-dev-guide/src/tests/ecosystem.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem.md b/src/doc/rustc-dev-guide/src/tests/ecosystem.md index 9e5b3a1e1c11d..ff9aca59ebe49 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem.md @@ -7,9 +7,10 @@ regressions and make informed decisions about the evolution of the language. ### Crater -Crater is a tool which runs tests on many thousands of public projects. This -tool has its own separate infrastructure for running, and is not run as part of -CI. See the [Crater chapter](crater.md) for more details. +Crater is a tool which runs tests on many thousands of public projects. +This tool has its own separate infrastructure for running, and is not run as part of +CI. +See the [Crater chapter](crater.md) for more details. ### `cargotest` @@ -23,8 +24,8 @@ there aren't any significant regressions: ### Large OSS Project builders -We have CI jobs that build large open-source Rust projects that are used as -regression tests in CI. Our integration jobs build the following projects: +We have CI jobs that build large open-source Rust projects that are used as regression tests in CI. +Our integration jobs build the following projects: - [Fuchsia](./ecosystem-test-jobs/fuchsia.md) - [Rust for Linux](./ecosystem-test-jobs/rust-for-linux.md) From a3afbd8d02503af933e435199425c88881feae4d Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Fri, 18 Sep 2026 04:32:20 -0500 Subject: [PATCH 42/76] use spawned `SBDebugger` instance --- src/etc/debugger_tester/lldb/batchmode.py | 43 ++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/etc/debugger_tester/lldb/batchmode.py b/src/etc/debugger_tester/lldb/batchmode.py index fdf6a34c01716..ebc7b7ff91577 100644 --- a/src/etc/debugger_tester/lldb/batchmode.py +++ b/src/etc/debugger_tester/lldb/batchmode.py @@ -16,6 +16,7 @@ import _thread as thread import os import re +import signal import sys import threading import time @@ -184,6 +185,27 @@ def dispatch_repr(var_name: str, breakpoint_index: int, frame: lldb.SBFrame) -> return check(var_name, breakpoint_index, frame) == Result.Ok +def quit_with_error(debugger: lldb.SBDebugger): + """Kills the parent LLDB process with a non-0 exit code""" + + # file handles aren't guaranteed to be flushed when python doesn't return control back to LLDB, + # so we need to do it manually + debugger.GetOutputFile().Flush() + debugger.GetErrorFile().Flush() + sys.stdout.flush() + sys.stderr.flush() + + # When using a debugger created from the python script (e.g. `lldb.SBDebugger.Create()`), this + # doesn't actually work, but it doesn't hurt to try =) + debugger.HandleCommand("quit 1") + + # Returning status codes using `sys.exit` doesn't work since we're in an LLDB managed python + # instance. Instead, we kill the PID, which happens to be the parent LLDB process. + # Note: We use SIGTERM because it works on linux and windows, unlike SIGKILL, and doesn't cause + # LLDB to spit out a backtrace like SIGABRT. + os.kill(os.getpid(), signal.SIGTERM) + + #################################################################################################### # ~main #################################################################################################### @@ -203,12 +225,12 @@ def main(): # Start the timeout watchdog start_watchdog() - # This is the debugger instance of the lldb executable that imported and ran this python script. - # There is some weird behavior around LLDB reassigning, clearing, or not updating their own - # references (like `lldb.debugger`) while a python function is actively running (i.e. if control - # is not given back to the REPL). To prevent LLDB from changing things out from under us, we - # store this reference locally. - debugger = lldb.debugger + # We use a new `SBDebugger` instance, since LLDB often doesn't update internal state/python + # state while a command is being run. Since the entirety of `batchmode` is executed via a + # `script` command, the parent LLDB never gets a chance to update. + # In LLDB <23 this was less of an issue, but a change in LLDB 23 made it difficult to create + # targets from the parent debugger instance. + debugger = lldb.SBDebugger.Create() # When we step or continue, don't return from the function until the process # stops. We do this by setting the async mode to false. @@ -283,13 +305,10 @@ def main(): print(f"Could not read debugging script '{script_path}'.") traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout) print("Aborting.") - # Returning status codes using `sys.exit` doesn't work since we're in an LLDB managed python - # instance. This command sets the exit code but *does not* kill LLDB, the debugee process, - # or the SBDebugger object. - debugger.HandleCommand("quit 1") + quit_with_error(debugger) except Exception as e: traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout) - debugger.HandleCommand("quit 1") + quit_with_error(debugger) else: # Executes if the `try` block throws no exceptions. if repr_cmd_run: # We save importing these until we actually see a repr command. This prevents us @@ -329,7 +348,7 @@ def main(): ) if not tested_all_types() or not tested_all_variables(): - debugger.HandleCommand("quit 1") + quit_with_error(debugger) elif BLESS: from lldb_providers import FEATURE_FLAGS From cff4b1ac281618214b91187850c4a427e898c2db Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 12:17:03 +0200 Subject: [PATCH 43/76] reduce indentation --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 92143aaa1fb87..26f37e0a9b238 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -182,15 +182,15 @@ fn lengthen_lines(content: &str, limit: usize) -> String { new_content[new_n] = format!("{line} {}", next_line.trim_start()); new_content.remove(new_n + 1); skip_next = true; - } else { - const SEP: &str = ", "; - let Some((before_comma, after_comma)) = next_line.split_once(SEP) else { continue }; - if line.len() + before_comma.len() < limit - SEP.len() { - new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); - new_n += 1; - new_content[new_n] = after_comma.to_owned(); - skip_next = true; - } + continue; + } + const SEP: &str = ", "; + let Some((before_comma, after_comma)) = next_line.split_once(SEP) else { continue }; + if line.len() + before_comma.len() < limit - SEP.len() { + new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); + new_n += 1; + new_content[new_n] = after_comma.to_owned(); + skip_next = true; } } new_content.join("\n") + "\n" From 6f51268f743b1bdb38a9d8f4276869be4490b42f Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 12:47:18 +0200 Subject: [PATCH 44/76] split some more lines on commas --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 26f37e0a9b238..04e6fa3df19d8 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -185,12 +185,22 @@ fn lengthen_lines(content: &str, limit: usize) -> String { continue; } const SEP: &str = ", "; - let Some((before_comma, after_comma)) = next_line.split_once(SEP) else { continue }; - if line.len() + before_comma.len() < limit - SEP.len() { - new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); - new_n += 1; - new_content[new_n] = after_comma.to_owned(); - skip_next = true; + if next_line.contains(SEP) { + let (before_comma, after_comma) = next_line.split_once(SEP).unwrap(); + if line.len() + before_comma.len() < limit - SEP.len() { + new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); + new_n += 1; + new_content[new_n] = after_comma.to_owned(); + skip_next = true; + } + } else if line.contains(SEP) { + let (before_comma, after_comma) = line.rsplit_once(SEP).unwrap(); + if after_comma.len() + next_line.len() < limit { + new_content[new_n] = format!("{before_comma}{}", SEP.trim_end()); + new_n += 1; + new_content[new_n] = format!("{after_comma} {next_line}"); + skip_next = true; + } } } new_content.join("\n") + "\n" @@ -334,14 +344,13 @@ fn should_pass() { } #[test] -#[ignore] fn split_on_comma_of_current_line() { let original = " -Each derived value has a dependency on other values, which could themselves be either base or +Each derived value has a dependency, on other values, which could themselves be either base or derived. "; let expected = " -Each derived value has a dependency on other values, +Each derived value has a dependency, on other values, which could themselves be either base or derived. "; assert_eq!(expected, lengthen_lines(original, 100)) From 0999e10ab660045ffb879c5b0a09c0c086821aa4 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 12:47:58 +0200 Subject: [PATCH 45/76] generic --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 04e6fa3df19d8..555f14abe8e72 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -186,19 +186,19 @@ fn lengthen_lines(content: &str, limit: usize) -> String { } const SEP: &str = ", "; if next_line.contains(SEP) { - let (before_comma, after_comma) = next_line.split_once(SEP).unwrap(); - if line.len() + before_comma.len() < limit - SEP.len() { - new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); + let (before_sep, after_sep) = next_line.split_once(SEP).unwrap(); + if line.len() + before_sep.len() < limit - SEP.len() { + new_content[new_n] = format!("{line} {before_sep}{}", SEP.trim_end()); new_n += 1; - new_content[new_n] = after_comma.to_owned(); + new_content[new_n] = after_sep.to_owned(); skip_next = true; } } else if line.contains(SEP) { - let (before_comma, after_comma) = line.rsplit_once(SEP).unwrap(); - if after_comma.len() + next_line.len() < limit { - new_content[new_n] = format!("{before_comma}{}", SEP.trim_end()); + let (before_sep, after_sep) = line.rsplit_once(SEP).unwrap(); + if after_sep.len() + next_line.len() < limit { + new_content[new_n] = format!("{before_sep}{}", SEP.trim_end()); new_n += 1; - new_content[new_n] = format!("{after_comma} {next_line}"); + new_content[new_n] = format!("{after_sep} {next_line}"); skip_next = true; } } From bf9557a9be5915c529ef4f3c5e36dcec2da1cee2 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 12:48:23 +0200 Subject: [PATCH 46/76] sembr src/tests/intro.md --- src/doc/rustc-dev-guide/src/tests/intro.md | 87 +++++++++++----------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/intro.md b/src/doc/rustc-dev-guide/src/tests/intro.md index 82fb9597cb500..162f8b836fadb 100644 --- a/src/doc/rustc-dev-guide/src/tests/intro.md +++ b/src/doc/rustc-dev-guide/src/tests/intro.md @@ -1,9 +1,9 @@ # Testing the compiler -The Rust project runs a wide variety of different tests, orchestrated by the -build system (`./x test`). This section gives a brief overview of the different -testing tools. Subsequent chapters dive into [running tests](running.md) and -[adding new tests](adding.md). +The Rust project runs a wide variety of different tests, +orchestrated by the build system (`./x test`). +This section gives a brief overview of the different testing tools. +Subsequent chapters dive into [running tests](running.md) and [adding new tests](adding.md). ## Kinds of tests @@ -12,11 +12,10 @@ Almost all of them are driven by `./x test`, with some exceptions noted below. ### Compiletest -The main test harness for testing the compiler itself is a tool called -[compiletest]. +The main test harness for testing the compiler itself is a tool called [compiletest]. -[compiletest] supports running different styles of tests, organized into *test -suites*. A *test mode* may provide common presets/behavior for a set of *test +[compiletest] supports running different styles of tests, +organized into *test suites*. A *test mode* may provide common presets/behavior for a set of *test suites*. [compiletest]-supported tests are located in the [`tests`] directory. The [Compiletest chapter][compiletest] goes into detail on how to use this tool. @@ -28,9 +27,9 @@ The [Compiletest chapter][compiletest] goes into detail on how to use this tool. ### Package tests -The standard library and many of the compiler packages include typical Rust -`#[test]` unit tests, integration tests, and documentation tests. You can pass a -path to `./x test` for almost any package in the `library/` or `compiler/` +The standard library and many of the compiler packages include typical Rust `#[test]` unit tests, +integration tests, and documentation tests. +You can pass a path to `./x test` for almost any package in the `library/` or `compiler/` directory, and `x` will essentially run `cargo test` on that package. Examples: @@ -41,13 +40,14 @@ Examples: | `./x test library/core` | Runs tests on `core` only | | `./x test compiler/rustc_data_structures` | Runs tests on `rustc_data_structures` | -The standard library relies very heavily on documentation tests to cover its -functionality. However, unit tests and integration tests can also be used as -needed. Almost all of the compiler packages have doctests disabled. +The standard library relies very heavily on documentation tests to cover its functionality. +However, unit tests and integration tests can also be used as needed. +Almost all of the compiler packages have doctests disabled. All standard library and compiler unit tests are placed in separate `tests` file -(which is enforced in [tidy][tidy-unit-tests]). This ensures that when the test -file is changed, the crate does not need to be recompiled. For example: +(which is enforced in [tidy][tidy-unit-tests]). +This ensures that when the test file is changed, the crate does not need to be recompiled. +For example: ```rust,ignore #[cfg(test)] @@ -55,11 +55,9 @@ mod tests; ``` If it wasn't done this way, and you were working on something like `core`, that -would require recompiling the entire standard library, and the entirety of -`rustc`. +would require recompiling the entire standard library, and the entirety of `rustc`. -`./x test` includes some CLI options for controlling the behavior with these -package tests: +`./x test` includes some CLI options for controlling the behavior with these package tests: * `--doc` — Only runs documentation tests in the package. * `--all-targets` — Run all tests *except* documentation tests. @@ -69,8 +67,9 @@ package tests: ### Tidy -Tidy is a custom tool used for validating source code style and formatting -conventions, such as rejecting long lines. There is more information in the +Tidy is a custom tool used for validating source code style and formatting conventions, +such as rejecting long lines. +There is more information in the [section on coding conventions](../conventions.md#formatting) or the [Tidy Readme]. > Examples: `./x test tidy` @@ -80,9 +79,8 @@ conventions, such as rejecting long lines. There is more information in the ### Formatting -Rustfmt is integrated with the build system to enforce uniform style across the -compiler. The formatting check is automatically run by the Tidy tool mentioned -above. +Rustfmt is integrated with the build system to enforce uniform style across the compiler. +The formatting check is automatically run by the Tidy tool mentioned above. Examples: @@ -94,10 +92,10 @@ Examples: ### Book documentation tests -All of the books that are published have their own tests, primarily for -validating that the Rust code examples pass. Under the hood, these are -essentially using `rustdoc --test` on the markdown files. The tests can be run -by passing a path to a book to `./x test`. +All of the books that are published have their own tests, +primarily for validating that the Rust code examples pass. +Under the hood, these are essentially using `rustdoc --test` on the markdown files. +The tests can be run by passing a path to a book to `./x test`. > Example: `./x test src/doc/book` @@ -114,8 +112,8 @@ This requires building all of the documentation, which might take a while. ### `distcheck` -`distcheck` verifies that the source distribution tarball created by the build -system will unpack, build, and run all tests. +`distcheck` verifies that the source distribution tarball created by the build system will unpack, +build, and run all tests. ```console ./x test distcheck @@ -123,25 +121,24 @@ system will unpack, build, and run all tests. ### Tool tests -Packages that are included with Rust have all of their tests run as well. This -includes things such as cargo, clippy, rustfmt, miri, bootstrap (testing the +Packages that are included with Rust have all of their tests run as well. +This includes things such as cargo, clippy, rustfmt, miri, bootstrap (testing the Rust build system itself), etc. -Most of the tools are located in the [`src/tools`] directory. To run the tool's -tests, just pass its path to `./x test`. +Most of the tools are located in the [`src/tools`] directory. +To run the tool's tests, just pass its path to `./x test`. > Example: `./x test src/tools/cargo` Usually these tools involve running `cargo test` within the tool's directory. -If you want to run only a specified set of tests, append `--test-args -FILTER_NAME` to the command. +If you want to run only a specified set of tests, append `--test-args FILTER_NAME` to the command. > Example: `./x test src/tools/miri --test-args padding` -In CI, some tools are allowed to fail. Failures send notifications to the -corresponding teams, and is tracked on the [toolstate website]. More information -can be found in the [toolstate documentation]. +In CI, some tools are allowed to fail. +Failures send notifications to the corresponding teams, and is tracked on the [toolstate website]. +More information can be found in the [toolstate documentation]. [`src/tools`]: https://github.com/rust-lang/rust/tree/HEAD/src/tools/ [toolstate documentation]: https://forge.rust-lang.org/infra/toolstate.html @@ -150,14 +147,14 @@ can be found in the [toolstate documentation]. ### Ecosystem testing Rust tests integration with real-world code to catch regressions and make -informed decisions about the evolution of the language. There are several kinds -of ecosystem tests, including Crater. See the [Ecosystem testing -chapter](ecosystem.md) for more details. +informed decisions about the evolution of the language. +There are several kinds of ecosystem tests, including Crater. +See the [Ecosystem testing chapter](ecosystem.md) for more details. ### Performance testing -A separate infrastructure is used for testing and tracking performance of the -compiler. See the [Performance testing chapter](perf.md) for more details. +A separate infrastructure is used for testing and tracking performance of the compiler. +See the [Performance testing chapter](perf.md) for more details. ### Codegen backend testing From 5539256d3fe52002e1c79fa46c9d30d803baf4da Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:20:50 +0200 Subject: [PATCH 47/76] respect indents --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 555f14abe8e72..742671ee7b1dd 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -185,12 +185,14 @@ fn lengthen_lines(content: &str, limit: usize) -> String { continue; } const SEP: &str = ", "; + let indent = next_line.find(|ch: char| !ch.is_whitespace()).unwrap(); if next_line.contains(SEP) { let (before_sep, after_sep) = next_line.split_once(SEP).unwrap(); if line.len() + before_sep.len() < limit - SEP.len() { - new_content[new_n] = format!("{line} {before_sep}{}", SEP.trim_end()); + new_content[new_n] = + format!("{line} {}{}", before_sep.trim_start(), SEP.trim_end()); new_n += 1; - new_content[new_n] = after_sep.to_owned(); + new_content[new_n] = format!("{:indent$}{after_sep}", ""); skip_next = true; } } else if line.contains(SEP) { @@ -198,7 +200,8 @@ fn lengthen_lines(content: &str, limit: usize) -> String { if after_sep.len() + next_line.len() < limit { new_content[new_n] = format!("{before_sep}{}", SEP.trim_end()); new_n += 1; - new_content[new_n] = format!("{after_sep} {next_line}"); + new_content[new_n] = + format!("{:indent$}{after_sep} {}", "", next_line.trim_start()); skip_next = true; } } @@ -348,10 +351,16 @@ fn split_on_comma_of_current_line() { let original = " Each derived value has a dependency, on other values, which could themselves be either base or derived. + + Each derived value has a dependency, on other values, which could themselves be either base or + derived. "; let expected = " Each derived value has a dependency, on other values, which could themselves be either base or derived. + + Each derived value has a dependency, on other values, + which could themselves be either base or derived. "; assert_eq!(expected, lengthen_lines(original, 100)) } @@ -361,10 +370,16 @@ fn split_on_comma_of_next_line() { let original = " Because of canonicalization of regions and inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. + + Because of canonicalization of regions and + inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. "; let expected = " Because of canonicalization of regions and inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. + + Because of canonicalization of regions and inference variables, + encountering a cycle doesn't mean that we would get an infinite proof tree. "; assert_eq!(expected, lengthen_lines(original, 100)) } From 6e5231a052656febe9a6294f41fce84743a40bef Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:22:29 +0200 Subject: [PATCH 48/76] sembr src/tests/compiletest.md --- .../rustc-dev-guide/src/tests/compiletest.md | 171 +++++++++--------- 1 file changed, 87 insertions(+), 84 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index 4573a04d0281c..7b6091c8d3253 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -3,8 +3,8 @@ ## Introduction `compiletest` is the main test harness of the Rust test suite. -It allows test authors to organize large numbers of tests (the Rust compiler has many -thousands), efficient test execution (parallel execution is supported), and +It allows test authors to organize large numbers of tests (the Rust compiler has many thousands), +efficient test execution (parallel execution is supported), and allows the test author to configure behavior and expected results of both individual and groups of tests. @@ -23,14 +23,14 @@ individual and groups of tests. Tests are typically organized as a Rust source file with annotations in comments before and/or within the test code. -These comments serve to direct `compiletest` -on if or how to run the test, what behavior to expect, and more. +These comments serve to direct `compiletest` on if or how to run the test, +what behavior to expect, and more. See [directives](directives.md) and the test suite documentation below for more details on these annotations. See the [Adding new tests](adding.md) and [Best practices](best-practices.md) -chapters for a tutorial on creating a new test and advice on writing a good -test, and the [Running tests](running.md) chapter on how to run the test suite. +chapters for a tutorial on creating a new test and advice on writing a good test, +and the [Running tests](running.md) chapter on how to run the test suite. Arguments can be passed to compiletest using `--test-args` or by placing them after `--`, e.g. - `x test --test-args --force-rerun` @@ -49,8 +49,8 @@ You can use `x test --test-args All of the tests are in the [`tests`] directory. The tests are organized into "suites", with each suite in a separate subdirectory. -Each test suite behaves a -little differently, with different compiler behavior and different checks for correctness. +Each test suite behaves a little differently, +with different compiler behavior and different checks for correctness. For example, the [`tests/incremental`] directory contains tests for incremental compilation. The various suites are defined in [`src/tools/compiletest/src/common.rs`] in the `pub enum Mode` declaration. @@ -114,8 +114,8 @@ The `-Z unpretty` CLI option for `rustc` causes it to translate the input source into various different formats, such as the Rust source after macro expansion. The pretty-printer tests have several [directives](directives.md) described below. -These commands can significantly change the behavior of the test, but the -default behavior without any commands is to: +These commands can significantly change the behavior of the test, +but the default behavior without any commands is to: 1. Run `rustc -Zunpretty=normal` on the source file. 2. Run `rustc -Zunpretty=normal` on the output of the previous step. @@ -133,17 +133,17 @@ The directives for pretty-printing tests are: - `pretty-compare-only` causes a pretty test to only compare the pretty-printed output (stopping after step 3 from above). It will not try to compile the expanded output to type check it. - This is needed for a pretty-mode that does - not expand to valid Rust, or for other situations where the expanded output cannot be compiled. + This is needed for a pretty-mode that does not expand to valid Rust, + or for other situations where the expanded output cannot be compiled. - `pp-exact` is used to ensure a pretty-print test results in specific output. - If specified without a value, then it means the pretty-print output should - match the original source. - If specified with a value, as in `//@ - pp-exact:foo.pp`, it will ensure that the pretty-printed output matches the + If specified without a value, + then it means the pretty-print output should match the original source. + If specified with a value, as in `//@ pp-exact:foo.pp`, + it will ensure that the pretty-printed output matches the contents of the given file. Otherwise, if `pp-exact` is not specified, then - the pretty-printed output will be pretty-printed one more time, and the output - of the two pretty-printing rounds will be compared to ensure that the + the pretty-printed output will be pretty-printed one more time, + and the output of the two pretty-printing rounds will be compared to ensure that the pretty-printed output converges to a steady state. [`tests/pretty`]: https://github.com/rust-lang/rust/tree/HEAD/tests/pretty @@ -166,8 +166,8 @@ Each revision name must start with one of: To make the revisions unique, you should add a suffix like `rpass1` and `rpass2`. -To simulate changing the source, compiletest also passes a `--cfg` flag with the -current revision name. +To simulate changing the source, +compiletest also passes a `--cfg` flag with the current revision name. For example, this will run twice, simulating changing a function: @@ -221,9 +221,10 @@ A simple example of a test using `rustc_clean` is the [hello_world test]. > opt-in. For further context, see: > [Stabilizing the state of the debuginfo test suite](https://github.com/rust-lang/compiler-team/issues/1012) -The tests in [`tests/debuginfo`] test how debuginfo is interpreted by the supported debuggers, and -confirm our visualizers still work as expected. They build a program, launch a debugger, and issue -commands to the debugger. A single test can work with cdb, gdb, and lldb. +The tests in [`tests/debuginfo`] test how debuginfo is interpreted by the supported debuggers, +and confirm our visualizers still work as expected. +They build a program, launch a debugger, and issue commands to the debugger. +A single test can work with cdb, gdb, and lldb. Most tests should have the `//@ compile-flags: -g` directive or something similar to generate the appropriate debuginfo. @@ -245,8 +246,8 @@ The debugger values can be: The command to check the output are of the form `//@ $DEBUGGER-check:$OUTPUT` where `$OUTPUT` is the output to expect. -For example, the following will build the test, start the debugger, set a -breakpoint, launch the program, inspect a value, and check what the debugger prints: +For example, the following will build the test, start the debugger, set a breakpoint, +launch the program, inspect a value, and check what the debugger prints: ```rust,ignore //@ compile-flags: -g @@ -265,7 +266,8 @@ fn b() {} Additionally, there is a special command, `//@ $DEBUGGER-repr:$VAR_NAME` intended to verify variables (and their visualizers) with more granularity than can be achieved with simple string -comparison. This directive should be preferred over the `-command`/`-check` whenever possible. +comparison. +This directive should be preferred over the `-command`/`-check` whenever possible. > [!NOTE] > At time of writing (July 2026) this command is limited to LLDB, with an implementation coming soon @@ -279,8 +281,8 @@ This command effectivly desugars into: ``` The `repr $VAR_NAME` command is intercepted by special logic that uses the debuggers' API to inspect -data that isn't reflected in the variable's printed output. The variable in memory is compared -against input data stored in +data that isn't reflected in the variable's printed output. +The variable in memory is compared against input data stored in `tests/debuginfo//input/_input/.json` and provides detailed error messages on failure. @@ -305,8 +307,9 @@ the debugger currently being used: - `min-apple-lldb-version: 1703.0.236.21`/`min-llvm-lldb-version: 21.1.0` — ignores the test if the version of lldb is below the given version. Note: Apple's fork of LLDB (distributed with Xcode) uses a different versioning scheme that is not - easily mappable to LLVM's LLDB version numbers. As such, the version gates are specified by - vendor. Further info on manually checking version equivalence is available [here](../debuginfo/testing.md#lldb-versioning) + easily mappable to LLVM's LLDB version numbers. + As such, the version gates are specified by vendor. + Further info on manually checking version equivalence is available [here](../debuginfo/testing.md#lldb-versioning) - `rust-lldb` — ignores the test if lldb is not contain the Rust plugin. NOTE: The "Rust" version of LLDB doesn't exist anymore, so this will always be ignored. This should probably be removed. @@ -361,14 +364,14 @@ See the [FileCheck] documentation for a tutorial and more information. See also the [assembly tests](#assembly-tests) for a similar set of tests. By default, codegen tests will have `//@ needs-target-std` *implied* (that the -target needs to support std), *unless* the `#![no_std]`/`#![no_core]` attribute -was specified in the test source. +target needs to support std), +*unless* the `#![no_std]`/`#![no_core]` attribute was specified in the test source. You can override this behavior and explicitly -write `//@ needs-target-std` to only run the test when target supports std, even -if the test is `#![no_std]`/`#![no_core]`. +write `//@ needs-target-std` to only run the test when target supports std, +even if the test is `#![no_std]`/`#![no_core]`. -If you need to work with `#![no_std]` cross-compiling tests, consult the -[`minicore` test auxiliary](./minicore.md) chapter. +If you need to work with `#![no_std]` cross-compiling tests, +consult the [`minicore` test auxiliary](./minicore.md) chapter. [`tests/codegen-llvm`]: https://github.com/rust-lang/rust/tree/HEAD/tests/codegen-llvm [FileCheck]: https://llvm.org/docs/CommandGuide/FileCheck.html @@ -388,8 +391,8 @@ See the [FileCheck] documentation for a tutorial and more information. See also the [codegen tests](#codegen-tests) for a similar set of tests. -If you need to work with `#![no_std]` cross-compiling tests, consult the -[`minicore` test auxiliary](./minicore.md) chapter. +If you need to work with `#![no_std]` cross-compiling tests, +consult the [`minicore` test auxiliary](./minicore.md) chapter. [`tests/assembly-llvm`]: https://github.com/rust-lang/rust/tree/HEAD/tests/assembly-llvm @@ -444,10 +447,10 @@ There are several forms the `EMIT_MIR` comment can take: interested in the final state after an optimization. Some rare cases may want to use the "before" file for completeness. -- `// EMIT_MIR $MIR_PATH.diff` — where `$MIR_PATH` is the filename of the MIR - dump, such as `my_test_name.my_function.EarlyOtherwiseBranch`. - Compiletest will diff the `.before.mir` and `.after.mir` files, and compare the diff - output to the expected `.diff` file from the `EMIT_MIR` comment. +- `// EMIT_MIR $MIR_PATH.diff` — where `$MIR_PATH` is the filename of the MIR dump, + such as `my_test_name.my_function.EarlyOtherwiseBranch`. + Compiletest will diff the `.before.mir` and `.after.mir` files, + and compare the diff output to the expected `.diff` file from the `EMIT_MIR` comment. This is useful if you want to see how an optimization changes the MIR. @@ -457,8 +460,8 @@ There are several forms the `EMIT_MIR` comment can take: By default 32 bit and 64 bit targets use the same dump files, which can be problematic in the presence of pointers in constants or other bit width dependent things. -In that case you can add `// EMIT_MIR_FOR_EACH_BIT_WIDTH` to -your test, causing separate files to be generated for 32bit and 64bit systems. +In that case you can add `// EMIT_MIR_FOR_EACH_BIT_WIDTH` to your test, +causing separate files to be generated for 32bit and 64bit systems. [`tests/mir-opt`]: https://github.com/rust-lang/rust/tree/HEAD/tests/mir-opt @@ -491,8 +494,8 @@ Each test should be in a separate directory with a `rmake.rs` Rust program, called the *recipe*. A recipe will be compiled and executed by compiletest with the `run_make_support` library linked in. -If you need new utilities or functionality, consider extending and improving the -[`run_make_support`] library. +If you need new utilities or functionality, +consider extending and improving the [`run_make_support`] library. Compiletest directives like `//@ only-` or `//@ ignore-` are supported in `rmake.rs`, like in UI tests. @@ -524,11 +527,11 @@ Of course, some tests will not successfully *run* in this way. #### Using rust-analyzer with `rmake.rs` -Like other test programs, the `rmake.rs` scripts used by run-make tests do not -have rust-analyzer integration by default. +Like other test programs, +the `rmake.rs` scripts used by run-make tests do not have rust-analyzer integration by default. -To work around this when working on a particular test, temporarily create a -`Cargo.toml` file in the test's directory +To work around this when working on a particular test, +temporarily create a `Cargo.toml` file in the test's directory (e.g. `tests/run-make/sysroot-crates-are-unstable/Cargo.toml`) with these contents: @@ -589,8 +592,8 @@ Each mode also has an alias to run the coverage tests in just that mode: ./x test coverage-map -- tests/coverage/if.rs # runs the specified test in "coverage-map" mode only ``` -If a particular test should not be run in one of the coverage test modes for -some reason, use the `//@ ignore-coverage-map` or `//@ ignore-coverage-run` directives. +If a particular test should not be run in one of the coverage test modes for some reason, +use the `//@ ignore-coverage-map` or `//@ ignore-coverage-run` directives. #### `coverage-map` suite @@ -598,11 +601,11 @@ In `coverage-map` mode, these tests verify the mappings between source code regions and coverage counters that are emitted by LLVM. They compile the test with `--emit llvm-ir`, then use a custom tool ([`src/tools/coverage-dump`]) to extract and pretty-print the coverage mappings embedded in the IR. -These tests don't require the profiler runtime, so they run in PR CI jobs and are easy to -run/bless locally. +These tests don't require the profiler runtime, +so they run in PR CI jobs and are easy to run/bless locally. -These coverage map tests can be sensitive to changes in MIR lowering or MIR -optimizations, producing mappings that are different but produce identical coverage reports. +These coverage map tests can be sensitive to changes in MIR lowering or MIR optimizations, +producing mappings that are different but produce identical coverage reports. As a rule of thumb, any PR that doesn't change coverage-specific code should **feel free to re-bless** the `coverage-map` tests as necessary, without @@ -612,19 +615,19 @@ worrying about the actual changes, as long as the `coverage-run` tests still pas In `coverage-run` mode, these tests perform an end-to-end test of coverage reporting. They compile a test program with coverage instrumentation, run that -program to produce raw coverage data, and then use LLVM tools to process that -data into a human-readable code coverage report. +program to produce raw coverage data, +and then use LLVM tools to process that data into a human-readable code coverage report. -Instrumented binaries need to be linked against the LLVM profiler runtime, so -`coverage-run` tests are **automatically skipped** unless the profiler runtime +Instrumented binaries need to be linked against the LLVM profiler runtime, +so `coverage-run` tests are **automatically skipped** unless the profiler runtime is enabled in `bootstrap.toml`: ```toml build.profiler = true ``` -This also means that they typically don't run in PR CI jobs, though they do run -as part of the full set of CI jobs used for merging. +This also means that they typically don't run in PR CI jobs, +though they do run as part of the full set of CI jobs used for merging. #### `coverage-run-rustdoc` suite @@ -638,8 +641,8 @@ This avoids having to build rustdoc when only running the main `coverage` suite. ### Crash tests -[`tests/crashes`] serve as a collection of tests that are expected to cause the -compiler to ICE, panic or crash in some other way, so that accidental fixes are tracked. +[`tests/crashes`] serve as a collection of tests that are expected to cause the compiler to ICE, +panic or crash in some other way, so that accidental fixes are tracked. Formerly, this was done at but doing it inside the rust-lang/rust testsuite is more convenient. @@ -659,8 +662,8 @@ When you do so, each issue number should be noted in the file name (`12345.rs` should suffice) and also inside the file by means of a `//@ known-bug: #12345` directive. Please [label][labeling] the relevant issues with `S-bug-has-test` once your PR is merged. -If you happen to fix one of the crashes, please move it to a fitting -subdirectory in `tests/ui` and give it a meaningful name. +If you happen to fix one of the crashes, +please move it to a fitting subdirectory in `tests/ui` and give it a meaningful name. Please add a doc comment at the top of the file explaining why this test exists. Even better will be if you can briefly explain how the example caused rustc to crash previously, and what was done to fix it. @@ -711,8 +714,8 @@ The `-L` flag is used to find the extern crates. `aux-crate` is very similar to `aux-build`. However, it uses the `--extern` flag to link to the extern crate to make the crate be available as an extern prelude. -That allows you to specify the additional syntax of the `--extern` flag, such as -renaming a dependency. +That allows you to specify the additional syntax of the `--extern` flag, +such as renaming a dependency. For example, `//@ aux-crate: foo=bar.rs` will compile `auxiliary/bar.rs` and make it available under the name `foo` within the test. This is similar to how Cargo does dependency renaming. @@ -722,8 +725,8 @@ For example, `//@ aux-crate: noprelude:foo=bar.rs`. `aux-bin` is similar to `aux-build` but will build a binary instead of a library. The binary will be available in `auxiliary/bin` relative to the working directory of the test. -`aux-codegen-backend` is similar to `aux-build`, but will then pass the compiled -dylib to `-Zcodegen-backend` when building the main file. +`aux-codegen-backend` is similar to `aux-build`, +but will then pass the compiled dylib to `-Zcodegen-backend` when building the main file. This will only work for tests in `tests/ui-fulldeps`, since it requires the use of compiler crates. ### Auxiliary proc-macro @@ -740,8 +743,8 @@ preset behavior compared to `aux-build` for the proc-macro test auxiliary: to produce a dylib for the aux crate. 3. The aux crate is made available to the test file via extern prelude with `--extern `. - Note that since UI tests default to edition - 2015, you still need to specify `extern ` unless the main + Note that since UI tests default to edition 2015, + you still need to specify `extern ` unless the main test file is using an edition that is 2018 or newer if you want to use the aux crate name in a `use` import. 4. The `proc_macro` crate is made available as an extern prelude module. @@ -794,8 +797,8 @@ This is done by adding a special directive at the top of the file: //@ revisions: foo bar baz ``` -This will result in the test being compiled (and tested) three times, once with -`--cfg foo`, once with `--cfg bar`, and once with `--cfg baz`. +This will result in the test being compiled (and tested) three times, once with `--cfg foo`, +once with `--cfg bar`, and once with `--cfg baz`. You can therefore use `#[cfg(foo)]` etc within the test to tweak each of these results. You can also customize directives and expected error messages to a particular revision. @@ -814,8 +817,8 @@ fn test_foo() { Multiple revisions can be specified in a comma-separated list, such as `//[foo,bar,baz]~^`. -In test suites that use the LLVM [FileCheck] tool, the current revision name is -also registered as an additional prefix for FileCheck directives: +In test suites that use the LLVM [FileCheck] tool, +the current revision name is also registered as an additional prefix for FileCheck directives: ```rust,ignore //@ revisions: NORMAL COVERAGE @@ -848,8 +851,8 @@ Normally, revision names mentioned in other directives and error annotations must correspond to an actual revision declared in a `revisions` directive. This is enforced by an `./x test tidy` check. -If a revision name needs to be temporarily removed from the revision list for -some reason, the above check can be suppressed by adding the revision name to an +If a revision name needs to be temporarily removed from the revision list for some reason, +the above check can be suppressed by adding the revision name to an `//@ unused-revision-names:` header instead. Specifying an unused name of `*` (i.e. `//@ unused-revision-names: *`) will @@ -857,10 +860,10 @@ permit any unused revision name to be mentioned. ## Compare modes -Compiletest can be run in different modes, called _compare modes_, which can be -used to compare the behavior of all tests with different compiler flags enabled. -This can help highlight what differences might appear with certain flags, and -check for any problems that might arise. +Compiletest can be run in different modes, called _compare modes_, +which can be used to compare the behavior of all tests with different compiler flags enabled. +This can help highlight what differences might appear with certain flags, +and check for any problems that might arise. To run the tests in a different mode, you need to pass the `--compare-mode` CLI flag: @@ -885,8 +888,8 @@ In CI, compare modes are only used in one Linux builder, and only with the follo This helps ensure that none of the debuginfo tests are affected when enabling split-DWARF. Note that compare modes are separate to [revisions](#revisions). -All revisions are tested when running `./x test tests/ui`, however compare-modes must be -manually run individually via the `--compare-mode` flag. +All revisions are tested when running `./x test tests/ui`, +however compare-modes must be manually run individually via the `--compare-mode` flag. ## Parallel frontend From 55ec44e149f4a008e91a9532803e14b3466a10f0 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:32:14 +0200 Subject: [PATCH 49/76] missing pause --- src/doc/rustc-dev-guide/src/tests/compiletest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index 7b6091c8d3253..ee42cf1fa1d87 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -642,7 +642,7 @@ This avoids having to build rustdoc when only running the main `coverage` suite. ### Crash tests [`tests/crashes`] serve as a collection of tests that are expected to cause the compiler to ICE, -panic or crash in some other way, so that accidental fixes are tracked. +panic, or crash in some other way, so that accidental fixes are tracked. Formerly, this was done at but doing it inside the rust-lang/rust testsuite is more convenient. From 65ee597275dff32783d7b308dddea75904f1a04b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:32:50 +0200 Subject: [PATCH 50/76] sembr src/tests/ecosystem-test-jobs/rust-for-linux.md --- .../ecosystem-test-jobs/rust-for-linux.md | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md index a6a7374b811be..e2d3bec5d86f7 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md @@ -7,23 +7,22 @@ support for the Rust programming language into the Linux kernel. If a PR breaks the Rust for Linux CI job, then: -- If the breakage was unintentional and seems spurious, then let [RfL][rfl-ping] - know and retry. - - If the PR is urgent and retrying doesn't fix it, then disable the CI job - temporarily (comment out the `image: x86_64-rust-for-linux` job in +- If the breakage was unintentional and seems spurious, then let [RfL][rfl-ping] know and retry. + - If the PR is urgent and retrying doesn't fix it, + then disable the CI job temporarily (comment out the `image: x86_64-rust-for-linux` job in `src/ci/github-actions/jobs.yml`). - If the breakage was unintentional, then change the PR to resolve the breakage. -- If the breakage was intentional, then let [RfL][rfl-ping] know and discuss - what will the kernel need to change. +- If the breakage was intentional, + then let [RfL][rfl-ping] know and discuss what will the kernel need to change. - If the PR is urgent, then disable the CI job temporarily (comment out the `image: x86_64-rust-for-linux` job in `src/ci/github-actions/jobs.yml`). - If the PR can wait a few days, then wait for RfL maintainers to provide a - new Linux kernel commit hash with the needed changes done, and apply it to - the PR, which would confirm the changes work (update the `LINUX_VERSION` + new Linux kernel commit hash with the needed changes done, and apply it to the PR, + which would confirm the changes work (update the `LINUX_VERSION` environment variable in `src/ci/docker/scripts/rfl-build.sh`). -If you need to contact the RfL developers, you can ping the [Rust for Linux][rfl-ping] -ping group to ask for help: +If you need to contact the RfL developers, +you can ping the [Rust for Linux][rfl-ping] ping group to ask for help: ```text @rustbot ping rfl @@ -31,17 +30,17 @@ ping group to ask for help: ## Building Rust for Linux in CI -Rust for Linux builds as part of the suite of bors tests that run before a pull -request is merged. +Rust for Linux builds as part of the suite of bors tests that run before a pull request is merged. -The workflow builds a stage1 sysroot of the Rust compiler, downloads the Linux -kernel, and tries to compile several Rust for Linux drivers and examples using -this sysroot. RfL uses several unstable compiler/language features, therefore -this workflow notifies us if a given compiler change would break it. +The workflow builds a stage1 sysroot of the Rust compiler, downloads the Linux kernel, +and tries to compile several Rust for Linux drivers and examples using +this sysroot. +RfL uses several unstable compiler/language features, +therefore this workflow notifies us if a given compiler change would break it. If you are worried that a pull request might break the Rust for Linux builder -and want to test it out before submitting it to the bors queue, simply ask -bors to run the try job that builds the Rust for Linux integration: +and want to test it out before submitting it to the bors queue, +simply ask bors to run the try job that builds the Rust for Linux integration: `@bors try jobs=x86_64-rust-for-linux`. [rfl-ping]: ../../notification-groups/rust-for-linux.md From 6c549a9e8a2c2afae49e9a758f87be349211a5e2 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:34:38 +0200 Subject: [PATCH 51/76] sembr src/tests/ecosystem-test-jobs/fuchsia.md --- .../src/tests/ecosystem-test-jobs/fuchsia.md | 98 +++++++++---------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index 75cf782a77025..124e8dfcda18d 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -14,8 +14,7 @@ Please contact the [fuchsia][fuchsia-ping] ping group and ask them for help. ## Building Fuchsia in CI -Fuchsia builds as part of the suite of bors tests that run before a pull request -is merged. +Fuchsia builds as part of the suite of bors tests that run before a pull request is merged. If you are worried that a pull request might break the Fuchsia builder and want to test it out before submitting it to the bors queue, simply ask bors to run @@ -23,13 +22,13 @@ the try job that builds the Fuchsia integration: `@bors try jobs=x86_64-fuchsia` ## Building Fuchsia locally -Because Fuchsia uses languages other than Rust, it does not use Cargo as a build -system. It also requires the toolchain build to be configured in a [certain -way][build-toolchain]. +Because Fuchsia uses languages other than Rust, it does not use Cargo as a build system. +It also requires the toolchain build to be configured in a [certain way][build-toolchain]. The recommended way to build Fuchsia is to use the Docker scripts that check out -and run a Fuchsia build for you. If you've run Docker tests before, you can -simply run this command from your Rust checkout to download and build Fuchsia +and run a Fuchsia build for you. +If you've run Docker tests before, +you can simply run this command from your Rust checkout to download and build Fuchsia using your local Rust toolchain. ``` @@ -40,20 +39,19 @@ See the [Testing with Docker](../docker.md) chapter for more details on how to r and debug jobs with Docker. Note that a Fuchsia checkout is *large* – as of this writing, a checkout and -build takes 46G of space – and as you might imagine, it takes a while to -complete. +build takes 46G of space – and as you might imagine, it takes a while to complete. ### Modifying the Fuchsia checkout The main reason you would want to build Fuchsia locally is because you need to -investigate a regression. After running a Docker build, you'll find the Fuchsia -checkout inside the `obj/fuchsia` directory of your Rust checkout. If you -modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to -`KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build -command above. This will reuse all the build results from before. +investigate a regression. +After running a Docker build, +you'll find the Fuchsia checkout inside the `obj/fuchsia` directory of your Rust checkout. + If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to +`KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build command above. +This will reuse all the build results from before. -You can find more options to customize the Fuchsia checkout in the -[build-fuchsia.sh] script. +You can find more options to customize the Fuchsia checkout in the [build-fuchsia.sh] script. ### Customizing the Fuchsia build @@ -69,14 +67,15 @@ to add this to your `$PATH` for some workflows. There are a few `fx` subcommands that are relevant, including: -- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and - runs GN. -- `fx build` builds the Fuchsia project using Ninja. It will automatically pick - up changes to build arguments and rerun GN. By default it builds everything, +- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. +- `fx build` builds the Fuchsia project using Ninja. + It will automatically pick up changes to build arguments and rerun GN. + By default it builds everything, but it also accepts target paths to build specific targets (see below). -- `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this - in the Rust CI build to avoid running codegen on most Rust targets. Underneath - it invokes Ninja, just like `fx build`. The clippy results are saved in json +- `fx clippy` runs Clippy on specific Rust targets (or all of them). + We use this in the Rust CI build to avoid running codegen on most Rust targets. + Underneath it invokes Ninja, just like `fx build`. + The clippy results are saved in json files inside the build output directory before being printed. #### Target paths @@ -87,20 +86,20 @@ GN uses paths like the following to identify build targets: //src/starnix/kernel:starnix_core ``` -The initial `//` means the root of the checkout, and the remaining slashes are -directory names. The string after `:` is the _target name_ of a target defined +The initial `//` means the root of the checkout, and the remaining slashes are directory names. +The string after `:` is the _target name_ of a target defined in the `BUILD.gn` file of that directory. -The target name can be omitted if it is the same as the directory name. In other -words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. +The target name can be omitted if it is the same as the directory name. +In other words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. These target paths are used inside `BUILD.gn` files to reference dependencies, and can also be used in `fx build`. #### Modifying compiler flags -You can put custom compiler flags inside a GN `config` that is added to a -target. As a simple example: +You can put custom compiler flags inside a GN `config` that is added to a target. +As a simple example: ``` config("everybody_loops") { @@ -114,20 +113,20 @@ rustc_binary("example") { } ``` -This will add the flag `-Zeverybody-loops` to rustc when building the `example` -target. Note that you can also use [`public_configs`] for a config to be added +This will add the flag `-Zeverybody-loops` to rustc when building the `example` target. +Note that you can also use [`public_configs`] for a config to be added to every target that depends on that target. -If you want to add a flag to every Rust target in the build, you can add -rustflags to the [`//build/config:compiler`] config or to the OS-specific -configs referenced in that file. Note that `cflags` and `ldflags` are ignored on -Rust targets. +If you want to add a flag to every Rust target in the build, +you can add rustflags to the [`//build/config:compiler`] config or to the OS-specific +configs referenced in that file. +Note that `cflags` and `ldflags` are ignored on Rust targets. #### Running ninja and rustc commands directly -Going down one layer, `fx build` invokes `ninja`, which in turn eventually -invokes `rustc`. All build actions are run inside the out directory, which is -usually `out/default` inside the Fuchsia checkout. +Going down one layer, `fx build` invokes `ninja`, which in turn eventually invokes `rustc`. +All build actions are run inside the out directory, +which is usually `out/default` inside the Fuchsia checkout. You can get ninja to print the actual command it invokes by forcing that command to fail, e.g. by adding a syntax error to one of the source files of the target. @@ -135,26 +134,25 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will -rebuild all the Rust targets. This can be done in a text editor and the contents -of the string do not matter, as long as it changes from one build to the next. -[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain -directory. +rebuild all the Rust targets. +This can be done in a text editor and the contents of the string do not matter, +as long as it changes from one build to the next. +[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. The Fuchsia website has more detailed documentation of the [build system]. #### Other tips and tricks When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` -command after the initial run so it won't rerun GN each time. If you do this you -can also comment out the version_string line to save a couple seconds. +command after the initial run so it won't rerun GN each time. +If you do this you can also comment out the version_string line to save a couple seconds. -`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the -initial build. +`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. ## Fuchsia target support -To learn more about Fuchsia target support, see the Fuchsia chapter in [the -rustc book][platform-support]. +To learn more about Fuchsia target support, +see the Fuchsia chapter in [the rustc book][platform-support]. [regressions]: https://gist.github.com/tmandry/7103eba4bd6a6fb0c439b5a90ae355fa [build-toolchain]: https://fuchsia.dev/fuchsia-src/development/build/rust_toolchain @@ -169,5 +167,5 @@ rustc book][platform-support]. [fuchsia-ping]: ../../notification-groups/fuchsia.md [^loc]: As of June 2024, Fuchsia had about 2 million lines of first-party Rust -code and a roughly equal amount of third-party code, as counted by tokei -(excluding comments and blanks). +code and a roughly equal amount of third-party code, +as counted by tokei (excluding comments and blanks). From 9f092518e89692b1c2490cb15688361b73b313e1 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:54:17 +0200 Subject: [PATCH 52/76] improve tests/ecosystem-test-jobs/fuchsia.md --- .../src/tests/ecosystem-test-jobs/fuchsia.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index 124e8dfcda18d..edbb3b93a0808 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -47,7 +47,7 @@ The main reason you would want to build Fuchsia locally is because you need to investigate a regression. After running a Docker build, you'll find the Fuchsia checkout inside the `obj/fuchsia` directory of your Rust checkout. - If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to +If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to `KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build command above. This will reuse all the build results from before. @@ -70,11 +70,11 @@ There are a few `fx` subcommands that are relevant, including: - `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. - `fx build` builds the Fuchsia project using Ninja. It will automatically pick up changes to build arguments and rerun GN. - By default it builds everything, + By default, it builds everything, but it also accepts target paths to build specific targets (see below). - `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this in the Rust CI build to avoid running codegen on most Rust targets. - Underneath it invokes Ninja, just like `fx build`. + Underneath, it invokes Ninja, just like `fx build`. The clippy results are saved in json files inside the build output directory before being printed. @@ -135,7 +135,7 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will rebuild all the Rust targets. -This can be done in a text editor and the contents of the string do not matter, +This can be done in a text editor, and the contents of the string do not matter, as long as it changes from one build to the next. [build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. @@ -145,7 +145,7 @@ The Fuchsia website has more detailed documentation of the [build system]. When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` command after the initial run so it won't rerun GN each time. -If you do this you can also comment out the version_string line to save a couple seconds. +If you do this, you can also comment out the version_string line to save a few seconds. `export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. From 04357edb2f8fe29ed8284509cd9ed0d2af80266a Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 14:54:30 +0200 Subject: [PATCH 53/76] sembr src/closure.md --- src/doc/rustc-dev-guide/src/closure.md | 109 +++++++++++++------------ 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/closure.md b/src/doc/rustc-dev-guide/src/closure.md index 427919cd57995..c59e512daef89 100644 --- a/src/doc/rustc-dev-guide/src/closure.md +++ b/src/doc/rustc-dev-guide/src/closure.md @@ -1,13 +1,12 @@ # Closure Capture Inference -This section describes how rustc handles closures. Closures in Rust are -effectively "desugared" into structs that contain the values they use (or -references to the values they use) from their creator's stack frame. rustc has -the job of figuring out which values a closure uses and how, so it can decide -whether to capture a given variable by shared reference, mutable reference, or -by move. rustc also has to figure out which of the closure traits ([`Fn`][fn], -[`FnMut`][fn_mut], or [`FnOnce`][fn_once]) a closure is capable of -implementing. +This section describes how rustc handles closures. +Closures in Rust are effectively "desugared" into structs that contain the values they use (or +references to the values they use) from their creator's stack frame. +rustc has the job of figuring out which values a closure uses and how, so it can decide +whether to capture a given variable by shared reference, mutable reference, or by move. +rustc also has to figure out which of the closure traits ([`Fn`][fn], +[`FnMut`][fn_mut], or [`FnOnce`][fn_once]) a closure is capable of implementing. [fn]: https://doc.rust-lang.org/std/ops/trait.Fn.html [fn_mut]:https://doc.rust-lang.org/std/ops/trait.FnMut.html @@ -31,8 +30,9 @@ fn main() { } ``` -Let's say the above is the content of a file called `immut.rs`. If we compile -`immut.rs` using the following command. The [`-Z dump-mir=all`][dump-mir] flag will cause +Let's say the above is the content of a file called `immut.rs`. +If we compile `immut.rs` using the following command. +The [`-Z dump-mir=all`][dump-mir] flag will cause `rustc` to generate and dump the [MIR][mir] to a directory called `mir_dump`. ```console > rustc +stage1 immut.rs -Z dump-mir=all @@ -43,8 +43,8 @@ Let's say the above is the content of a file called `immut.rs`. If we compile After we run this command, we will see a newly generated directory in our current working directory called `mir_dump`, which will contain several files. -If we look at file `rustc.main.-------.mir_map.0.mir`, we will find, among -other things, it also contains this line: +If we look at file `rustc.main.-------.mir_map.0.mir`, we will find, among other things, +it also contains this line: ```rust,ignore _4 = &_1; @@ -53,9 +53,9 @@ _3 = [closure@immut.rs:7:13: 7:36] { x: move _4 }; Note that in the MIR examples in this chapter, `_1` is `x`. -Here in first line `_4 = &_1;`, the `mir_dump` tells us that `x` was borrowed -as an immutable reference. This is what we would hope as our closure just -reads `x`. +Here in first line `_4 = &_1;`, +the `mir_dump` tells us that `x` was borrowed as an immutable reference. + This is what we would hope as our closure just reads `x`. ### Example 2 @@ -81,7 +81,8 @@ _4 = &mut _1; _3 = [closure@mut.rs:7:13: 10:6] { x: move _4 }; ``` This time along, in the line `_4 = &mut _1;`, we see that the borrow is changed to mutable borrow. -Fair enough! The closure increments `x` by 10. +Fair enough! +The closure increments `x` by 10. ### Example 3 @@ -104,33 +105,40 @@ fn main() { ```rust,ignore _6 = [closure@move.rs:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6 ``` -Here, `x` is directly moved into the closure and the access to it will not be permitted after the -closure. +Here, +`x` is directly moved into the closure and the access to it will not be permitted after the closure. ## Inferences in the compiler Now let's dive into rustc code and see how all these inferences are done by the compiler. Let's start with defining a term that we will be using quite a bit in the rest of the discussion - -*upvar*. An **upvar** is a variable that is local to the function where the closure is defined. So, -in the above examples, **x** will be an upvar to the closure. They are also sometimes referred to as +*upvar*. An **upvar** is a variable that is local to the function where the closure is defined. +So, +in the above examples, **x** will be an upvar to the closure. +They are also sometimes referred to as the *free variables* meaning they are not bound to the context of the closure. [`compiler/rustc_passes/src/upvars.rs`][upvars] defines a query called *upvars_mentioned* for this purpose. [upvars]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_passes/upvars/index.html -Other than lazy invocation, one other thing that distinguishes a closure from a -normal function is that it can use the upvars. It borrows these upvars from its surrounding -context; therefore the compiler has to determine the upvar's borrow type. The compiler starts with -assigning an immutable borrow type and lowers the restriction (that is, changes it from -**immutable** to **mutable** to **move**) as needed, based on the usage. In the Example 1 above, the +Other than lazy invocation, +one other thing that distinguishes a closure from a normal function is that it can use the upvars. +It borrows these upvars from its surrounding +context; therefore the compiler has to determine the upvar's borrow type. +The compiler starts with assigning an immutable borrow type and lowers the restriction (that is, +changes it from +**immutable** to **mutable** to **move**) as needed, based on the usage. +In the Example 1 above, the closure only uses the variable for printing but does not modify it in any way and therefore, in the -`mir_dump`, we find the borrow type for the upvar `x` to be immutable. In example 2, however, the -closure modifies `x` and increments it by some value. Because of this mutation, the compiler, which +`mir_dump`, we find the borrow type for the upvar `x` to be immutable. + In example 2, however, the closure modifies `x` and increments it by some value. + Because of this mutation, the compiler, which started off assigning `x` as an immutable reference type, has to adjust it as a mutable reference. Likewise in the third example, the closure drops the vector and therefore this requires the variable -`x` to be moved into the closure. Depending on the borrow kind, the closure has to implement the +`x` to be moved into the closure. +Depending on the borrow kind, the closure has to implement the appropriate trait: `Fn` trait for immutable borrow, `FnMut` for mutable borrow, and `FnOnce` for move semantics. @@ -141,17 +149,17 @@ declared in the file [`compiler/rustc_middle/src/ty/mod.rs`][ty]. [upvar]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/upvar/index.html [ty]:https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/index.html -Before we go any further, let's discuss how we can examine the flow of control through the rustc -codebase. For closures specifically, set the `RUSTC_LOG` env variable as below and collect the -output in a file: +Before we go any further, +let's discuss how we can examine the flow of control through the rustc codebase. +For closures specifically, +set the `RUSTC_LOG` env variable as below and collect the output in a file: ```console > RUSTC_LOG=rustc_hir_typeck::upvar rustc +stage1 -Z dump-mir=all \ <.rs file to compile> 2> ``` -This uses the stage1 compiler and enables `debug!` logging for the -`rustc_hir_typeck::upvar` module. +This uses the stage1 compiler and enables `debug!` logging for the `rustc_hir_typeck::upvar` module. The other option is to step through the code using lldb or gdb. @@ -160,8 +168,8 @@ The other option is to step through the code using lldb or gdb. 1. `b upvar.rs:134` // Setting the breakpoint on a certain line in the upvar.rs file 2. `r` // Run the program until it hits the breakpoint -Let's start with [`upvar.rs`][upvar]. This file has something called -the [`euv::ExprUseVisitor`] which walks the source of the closure and +Let's start with [`upvar.rs`][upvar]. +This file has something called the [`euv::ExprUseVisitor`] which walks the source of the closure and invokes a callback for each upvar that is borrowed, mutated, or moved. [`euv::ExprUseVisitor`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/expr_use_visitor/struct.ExprUseVisitor.html @@ -176,32 +184,33 @@ fn main() { } ``` -In the above example, our visitor will be called twice, for the lines marked 1 and 2, once for a -shared borrow and another one for a mutable borrow. It will also tell us what was borrowed. +In the above example, our visitor will be called twice, for the lines marked 1 and 2, +once for a shared borrow and another one for a mutable borrow. +It will also tell us what was borrowed. -The callbacks are defined by implementing the [`Delegate`] trait. The -[`InferBorrowKind`][ibk] type implements `Delegate` and keeps a map that -records for each upvar which mode of capture was required. The modes of capture -can be `ByValue` (moved) or `ByRef` (borrowed). For `ByRef` borrows, the possible -[`BorrowKind`]s are `ImmBorrow`, `UniqueImmBorrow`, `MutBorrow` as defined in the +The callbacks are defined by implementing the [`Delegate`] trait. +The [`InferBorrowKind`][ibk] type implements `Delegate` and keeps a map that +records for each upvar which mode of capture was required. +The modes of capture can be `ByValue` (moved) or `ByRef` (borrowed). +For `ByRef` borrows, the possible [`BorrowKind`]s are `ImmBorrow`, +`UniqueImmBorrow`, `MutBorrow` as defined in the [`compiler/rustc_middle/src/ty/mod.rs`][middle_ty]. [`BorrowKind`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.BorrowKind.html [middle_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/index.html `Delegate` defines a few different methods (the different callbacks): -**consume** for *move* of a variable, **borrow** for a *borrow* of some kind -(shared or mutable), and **mutate** when we see an *assignment* of something. +**consume** for *move* of a variable, **borrow** for a *borrow* of some kind (shared or mutable), +and **mutate** when we see an *assignment* of something. All of these callbacks have a common argument *cmt* which stands for Category, -Mutability and Type and is defined in -[`compiler/rustc_hir_typeck/src/expr_use_visitor.rs`][cmt]. Borrowing from the code -comments, "`cmt` is a complete categorization of a value indicating where it +Mutability and Type and is defined in [`compiler/rustc_hir_typeck/src/expr_use_visitor.rs`][cmt]. +Borrowing from the code comments, "`cmt` is a complete categorization of a value indicating where it originated and how it is located, as well as the mutability of the memory in which the value is stored". Based on the callback (consume, borrow etc.), we -will call the relevant `adjust_upvar_borrow_kind_for_` and pass the -`cmt` along. Once the borrow type is adjusted, we store it in the table, which -basically says what borrows were made for each closure. +will call the relevant `adjust_upvar_borrow_kind_for_` and pass the `cmt` along. +Once the borrow type is adjusted, we store it in the table, +which basically says what borrows were made for each closure. ```rust,ignore self.tables From b0f83b7d0a099fe4af956785ae79aa0edc2d5860 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 18 Sep 2026 14:55:56 +0200 Subject: [PATCH 54/76] Update `browser-ui-test` version to `0.25.2` --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d9a3ced805ba5..876d81c1d60ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "browser-ui-test": "^0.25.0", + "browser-ui-test": "^0.25.2", "es-check": "^9.4.4", "eslint": "^8.57.1", "typescript": "^5.8.3" diff --git a/yarn.lock b/yarn.lock index 55c862d1e75dd..e49c9209142f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -154,10 +154,10 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browser-ui-test@^0.25.0: - version "0.25.1" - resolved "https://registry.yarnpkg.com/browser-ui-test/-/browser-ui-test-0.25.1.tgz#c7f22a5e2b9e51be4ba34df3adf7bd7a9249bce6" - integrity sha512-woRwKU1dPBIwYmCI6npox8qlPO0WQ8GZH2YbL39mNkiWymByebiB4EK0PlaGMbmEja0MEqfMQD+d33LCW4S2AA== +browser-ui-test@^0.25.2: + version "0.25.2" + resolved "https://registry.yarnpkg.com/browser-ui-test/-/browser-ui-test-0.25.2.tgz#31db7386497b3eef4d79e236bd2091c1162148a9" + integrity sha512-74njL1/xjg5UumbhQblWa6oAn/PcaOnlJmoSe9FabJaXrLsI2f8DJ3B8vunvCJwZa90V4Jm94xyg/JPi+l+zZQ== dependencies: css-unit-converter "^1.1.2" pngjs "^3.4.0" From 14e479d21eef2cdc6934a7078c085747733736fe Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 15:09:29 +0200 Subject: [PATCH 55/76] improve closure.md --- src/doc/rustc-dev-guide/src/closure.md | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/closure.md b/src/doc/rustc-dev-guide/src/closure.md index c59e512daef89..33206ed885bc5 100644 --- a/src/doc/rustc-dev-guide/src/closure.md +++ b/src/doc/rustc-dev-guide/src/closure.md @@ -31,9 +31,10 @@ fn main() { ``` Let's say the above is the content of a file called `immut.rs`. -If we compile `immut.rs` using the following command. -The [`-Z dump-mir=all`][dump-mir] flag will cause -`rustc` to generate and dump the [MIR][mir] to a directory called `mir_dump`. +If we compile `immut.rs` using the following command, +the [`-Z dump-mir=all`][dump-mir] flag will cause +`rustc` to generate and dump the [MIR] to a directory called `mir_dump`. + ```console > rustc +stage1 immut.rs -Z dump-mir=all ``` @@ -55,7 +56,7 @@ Note that in the MIR examples in this chapter, `_1` is `x`. Here in first line `_4 = &_1;`, the `mir_dump` tells us that `x` was borrowed as an immutable reference. - This is what we would hope as our closure just reads `x`. +This is what we would hope as our closure just reads `x`. ### Example 2 @@ -105,19 +106,19 @@ fn main() { ```rust,ignore _6 = [closure@move.rs:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6 ``` -Here, -`x` is directly moved into the closure and the access to it will not be permitted after the closure. +Here, `x` is directly moved into the closure, +and the access to it will not be permitted after the closure. ## Inferences in the compiler Now let's dive into rustc code and see how all these inferences are done by the compiler. Let's start with defining a term that we will be using quite a bit in the rest of the discussion - -*upvar*. An **upvar** is a variable that is local to the function where the closure is defined. -So, -in the above examples, **x** will be an upvar to the closure. -They are also sometimes referred to as -the *free variables* meaning they are not bound to the context of the closure. +*upvar*. +An **upvar** is a variable that is local to the function where the closure is defined. +So, in the above examples, **x** will be an upvar to the closure. +They are also sometimes referred to as the *free variables*, +meaning they are not bound to the context of the closure. [`compiler/rustc_passes/src/upvars.rs`][upvars] defines a query called *upvars_mentioned* for this purpose. @@ -127,9 +128,8 @@ Other than lazy invocation, one other thing that distinguishes a closure from a normal function is that it can use the upvars. It borrows these upvars from its surrounding context; therefore the compiler has to determine the upvar's borrow type. -The compiler starts with assigning an immutable borrow type and lowers the restriction (that is, -changes it from -**immutable** to **mutable** to **move**) as needed, based on the usage. +The compiler starts with assigning an immutable borrow type and lowers the restriction +(that is, changes it from **immutable** to **mutable** to **move**) as needed, based on the usage. In the Example 1 above, the closure only uses the variable for printing but does not modify it in any way and therefore, in the `mir_dump`, we find the borrow type for the upvar `x` to be immutable. From f8f15cf79fcb06ae2b6004285c9092c020dc7e98 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 15:22:28 +0200 Subject: [PATCH 56/76] fix whitespace --- src/doc/rustc-dev-guide/src/offload/installation.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index baf689f458077..2bb7ea98f6d5b 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -4,12 +4,12 @@ For now, everyone however still needs to build rustc from source to use all features of it. ## Rustup installation. + If you are on `x86_64` Linux, you can install the nightly toolchain with: ```console rustup +nightly component add offload ``` - ## Build instructions Otherwise you need to clone and configure the Rust repository: @@ -35,9 +35,8 @@ rustup toolchain link offload build/host/stage1 rustup toolchain install nightly # enables -Z unstable-options ``` - - ## Build instruction for LLVM itself + ```console git clone git@github.com:llvm/llvm-project cd llvm-project @@ -49,8 +48,8 @@ ninja install ``` This gives you a working LLVM build. - ## Testing + Run this test script for offload-specific tests: ```console ./x test --stage 1 tests/codegen-llvm/gpu_offload From c60430f10f465941ad8818e73230e950f170b572 Mon Sep 17 00:00:00 2001 From: beetrees Date: Fri, 18 Sep 2026 12:27:37 +0100 Subject: [PATCH 57/76] Add missing `#[repr(C)]` in UI, codegen and assembly tests --- tests/assembly-llvm/asm/amdgpu-vec-types.rs | 3 ++- tests/assembly-llvm/naked-functions/wasm32.rs | 1 - tests/assembly-llvm/reg-struct-return.rs | 3 +++ tests/codegen-llvm/abi-x86_64_sysv.rs | 2 ++ tests/codegen-llvm/bpf-abi/indirect-return.rs | 1 + tests/codegen-llvm/complex-abi.rs | 3 ++- tests/codegen-llvm/regparm-inreg.rs | 11 ++++++----- .../riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs | 3 ++- tests/codegen-llvm/scalable-vectors/memcpy.rs | 3 ++- tests/ui/abi/abi-sysv64-arg-passing.rs | 3 ++- tests/ui/abi/abi-sysv64-register-usage.rs | 2 +- tests/ui/abi/arm-unadjusted-intrinsic.rs | 2 +- tests/ui/abi/compatibility.rs | 2 +- tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs | 2 +- tests/ui/abi/extern/extern-c-method-return-struct.rs | 3 ++- tests/ui/abi/extern/extern-pass-FiveU16s.rs | 2 +- tests/ui/abi/extern/extern-pass-TwoU16s.rs | 2 +- tests/ui/abi/extern/extern-pass-TwoU32s.rs | 2 +- tests/ui/abi/extern/extern-pass-TwoU64s.rs | 2 +- tests/ui/abi/extern/extern-pass-TwoU8s.rs | 2 +- tests/ui/abi/extern/extern-pass-empty.rs | 2 +- tests/ui/abi/extern/extern-return-FiveU16s.rs | 2 +- tests/ui/abi/extern/extern-return-TwoU16s.rs | 2 +- tests/ui/abi/extern/extern-return-TwoU32s.rs | 2 +- tests/ui/abi/extern/extern-return-TwoU64s.rs | 2 +- tests/ui/abi/extern/extern-return-TwoU8s.rs | 2 +- tests/ui/abi/foreign/foreign-fn-with-byval.rs | 2 +- tests/ui/abi/issue-28676.rs | 2 +- .../ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs | 2 +- .../issue-97463-broken-abi-leaked-uninit-data.rs | 2 -- tests/ui/abi/large-byval-align.rs | 3 +-- tests/ui/abi/non-rustic-unsized.rs | 2 +- tests/ui/abi/simd-abi-checks-avx.rs | 2 +- tests/ui/abi/simd-abi-checks-s390x.rs | 2 +- tests/ui/abi/simd-abi-checks-sse.rs | 2 +- .../abi/unsized-args-in-c-abi-issues-94223-115845.rs | 2 +- tests/ui/ffi/ffi-struct-size-alignment.rs | 2 +- 37 files changed, 49 insertions(+), 40 deletions(-) diff --git a/tests/assembly-llvm/asm/amdgpu-vec-types.rs b/tests/assembly-llvm/asm/amdgpu-vec-types.rs index 1613e603fca49..8ff4208636bc6 100644 --- a/tests/assembly-llvm/asm/amdgpu-vec-types.rs +++ b/tests/assembly-llvm/asm/amdgpu-vec-types.rs @@ -14,11 +14,12 @@ #![allow( asm_sub_register, improper_gpu_kernel_arg, - improper_ctypes_definitions, non_camel_case_types, unused_assignments, unused_variables )] +#![deny(unfulfilled_lint_expectations)] +#![expect(improper_ctypes_definitions)] extern crate minicore; use minicore::simd::*; diff --git a/tests/assembly-llvm/naked-functions/wasm32.rs b/tests/assembly-llvm/naked-functions/wasm32.rs index e2a2ab94c8a33..b2754029021bf 100644 --- a/tests/assembly-llvm/naked-functions/wasm32.rs +++ b/tests/assembly-llvm/naked-functions/wasm32.rs @@ -99,7 +99,6 @@ extern "C" fn fn_i64_i64(num: i64) -> i64 { // wasm32-unknown: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm32-wasip1: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () -#[allow(improper_ctypes_definitions)] #[no_mangle] #[unsafe(naked)] extern "C" fn fn_i128_i128(num: i128) -> i128 { diff --git a/tests/assembly-llvm/reg-struct-return.rs b/tests/assembly-llvm/reg-struct-return.rs index d364954abe30d..59e40c9e6fb12 100644 --- a/tests/assembly-llvm/reg-struct-return.rs +++ b/tests/assembly-llvm/reg-struct-return.rs @@ -23,6 +23,7 @@ use minicore::*; // Verifies ABI changes for small structs, where both fields fit into one register. // WITH is expected to use register return, WITHOUT should use hidden pointer. mod Small { + #[repr(C)] struct SmallStruct { a: i8, b: i8, @@ -66,6 +67,7 @@ mod Small { // WITH is expected to still use register return, WITHOUT should use hidden // pointer. mod Pivot { + #[repr(C)] struct PivotStruct { a: i32, b: i32, @@ -109,6 +111,7 @@ mod Pivot { // maximum size for reg-struct-return (8 bytes). // Here, the hidden pointer convention should be used even when `-Zreg-struct-return` is set. mod Large { + #[repr(C)] struct LargeStruct { a: i32, b: i32, diff --git a/tests/codegen-llvm/abi-x86_64_sysv.rs b/tests/codegen-llvm/abi-x86_64_sysv.rs index 09909f994d652..b8912c91ebaee 100644 --- a/tests/codegen-llvm/abi-x86_64_sysv.rs +++ b/tests/codegen-llvm/abi-x86_64_sysv.rs @@ -4,12 +4,14 @@ #![crate_type = "lib"] +#[repr(C)] pub struct S24 { a: i8, b: i8, c: i8, } +#[repr(C)] pub struct S48 { a: i16, b: i16, diff --git a/tests/codegen-llvm/bpf-abi/indirect-return.rs b/tests/codegen-llvm/bpf-abi/indirect-return.rs index c285bd9431c58..437f4ab0f3abc 100644 --- a/tests/codegen-llvm/bpf-abi/indirect-return.rs +++ b/tests/codegen-llvm/bpf-abi/indirect-return.rs @@ -11,6 +11,7 @@ extern crate minicore; +#[repr(C)] struct Big { a: [u16; 32], b: u64, diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 4ba7ecad764f2..c900c3debd504 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -103,7 +103,8 @@ #![feature(no_core, lang_items, f16, f128)] #![no_core] -#![allow(improper_ctypes)] // only Complex<{float}> is guaranteed to be ABI-compatible for now +// only Complex<{float}> is guaranteed to be ABI-compatible for now +#![expect(improper_ctypes_definitions)] #![crate_type = "lib"] extern crate minicore; diff --git a/tests/codegen-llvm/regparm-inreg.rs b/tests/codegen-llvm/regparm-inreg.rs index 77d4c206071e7..bf6bc4dd7bf0e 100644 --- a/tests/codegen-llvm/regparm-inreg.rs +++ b/tests/codegen-llvm/regparm-inreg.rs @@ -52,14 +52,15 @@ pub mod tests { #[no_mangle] pub extern "thiscall" fn f6(_: i32, _: i32, _: i32) {} + #[repr(C)] struct S1 { x1: i32, } - // regparm0: @f7(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3, i32 noundef %_4) - // regparm1: @f7(i32 inreg noundef %_1, i32 noundef %_2, i32 noundef %_3, i32 noundef %_4) - // regparm2: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3, i32 noundef %_4) - // regparm3: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 inreg noundef %_3, - // regparm3-SAME: i32 noundef %_4) + // regparm0: @f7(i32 noundef %_1, i32 noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, i32 noundef %_4) + // regparm1: @f7(i32 inreg noundef %_1, i32 noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, i32 noundef %_4) + // regparm2: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, i32 noundef %_4) + // regparm3: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, + // regparm3-SAME: i32 inreg noundef %_4) #[no_mangle] pub extern "C" fn f7(_: i32, _: i32, _: S1, _: i32) {} diff --git a/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs b/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs index 0f5a449ead133..ae806aa64f513 100644 --- a/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs +++ b/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs @@ -5,7 +5,7 @@ #![crate_type = "lib"] #![no_core] #![feature(no_core, lang_items)] -#![allow(improper_ctypes)] +#![deny(unfulfilled_lint_expectations, improper_ctypes_definitions)] extern crate minicore; use minicore::*; @@ -59,6 +59,7 @@ pub extern "C" fn f_fp_scalar_2(x: f64) -> f64 { pub struct Empty {} // CHECK: define void @f_agg_empty_struct() +#[expect(improper_ctypes_definitions)] #[no_mangle] pub extern "C" fn f_agg_empty_struct(e: Empty) -> Empty { e diff --git a/tests/codegen-llvm/scalable-vectors/memcpy.rs b/tests/codegen-llvm/scalable-vectors/memcpy.rs index 859f50b5b1178..30d4b0d17ebe1 100644 --- a/tests/codegen-llvm/scalable-vectors/memcpy.rs +++ b/tests/codegen-llvm/scalable-vectors/memcpy.rs @@ -5,12 +5,13 @@ #![crate_type = "lib"] #![feature(simd_ffi)] #![feature(stdarch_aarch64_sve)] +#![deny(unfulfilled_lint_expectations)] // Test that `vscale * size` is generated for `memcpy` of scalable vector types use std::arch::aarch64::*; -#[allow(improper_ctypes)] +#[expect(improper_ctypes)] unsafe extern "C" { fn svcreate2_s16_wrapper(__dst: *mut svint16x2_t, x0: *const svint16_t, x1: *const svint16_t); fn svcreate3_s16_wrapper( diff --git a/tests/ui/abi/abi-sysv64-arg-passing.rs b/tests/ui/abi/abi-sysv64-arg-passing.rs index 362a1862f9d4c..e21ca94aa6e8f 100644 --- a/tests/ui/abi/abi-sysv64-arg-passing.rs +++ b/tests/ui/abi/abi-sysv64-arg-passing.rs @@ -34,7 +34,6 @@ // the sysv64 ABI on Windows. #[allow(dead_code)] -#[allow(improper_ctypes)] #[cfg(target_arch = "x86_64")] mod tests { @@ -87,6 +86,7 @@ mod tests { #[derive(Copy, Clone)] pub struct Quad { a: u64, b: u64, c: u64, d: u64 } + #[repr(C)] #[derive(Copy, Clone)] pub struct QuadFloats { a: f32, b: f32, c: f32, d: f32 } @@ -113,6 +113,7 @@ mod tests { pub fn rust_dbg_extern_identity_u32(v: u32) -> u32; pub fn rust_dbg_extern_identity_u64(v: u64) -> u64; pub fn rust_dbg_extern_identity_double(v: f64) -> f64; + #[expect(improper_ctypes)] pub fn rust_dbg_extern_empty_struct(v1: ManyInts, e: Empty, v2: ManyInts); pub fn rust_dbg_extern_identity_TwoU8s(v: TwoU8s) -> TwoU8s; pub fn rust_dbg_extern_identity_TwoU16s(v: TwoU16s) -> TwoU16s; diff --git a/tests/ui/abi/abi-sysv64-register-usage.rs b/tests/ui/abi/abi-sysv64-register-usage.rs index cf1620db6f7be..576af8e696438 100644 --- a/tests/ui/abi/abi-sysv64-register-usage.rs +++ b/tests/ui/abi/abi-sysv64-register-usage.rs @@ -45,11 +45,11 @@ pub extern "sysv64" fn all_the_registers( // this struct contains 8 i64's, while only 6 can be passed in registers. #[cfg(target_arch = "x86_64")] #[derive(PartialEq, Eq, Debug)] +#[repr(C)] pub struct LargeStruct(i64, i64, i64, i64, i64, i64, i64, i64); #[cfg(target_arch = "x86_64")] #[inline(never)] -#[allow(improper_ctypes_definitions)] pub extern "sysv64" fn large_struct_by_val(mut foo: LargeStruct) -> LargeStruct { foo.0 *= 1; foo.1 *= 2; diff --git a/tests/ui/abi/arm-unadjusted-intrinsic.rs b/tests/ui/abi/arm-unadjusted-intrinsic.rs index f20fda4c61534..4340369d5ecc7 100644 --- a/tests/ui/abi/arm-unadjusted-intrinsic.rs +++ b/tests/ui/abi/arm-unadjusted-intrinsic.rs @@ -28,7 +28,7 @@ impl Copy for int8x16x4_t {} #[target_feature(enable = "neon")] #[cfg_attr(target_arch = "arm", target_feature(enable = "v7"))] pub unsafe fn vld1q_s8_x4(a: *const i8) -> int8x16x4_t { - #[allow(improper_ctypes)] + #[expect(improper_ctypes)] extern "llvm-intrinsic" { #[cfg_attr(target_arch = "arm", link_name = "llvm.arm.neon.vld1x4.v16i8.p0i8")] #[cfg_attr(target_arch = "aarch64", link_name = "llvm.aarch64.neon.ld1x4.v16i8.p0i8")] diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index e2496726f4b3f..c0a124208b21e 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -89,7 +89,7 @@ #![feature(no_core, rustc_attrs, lang_items)] #![feature(unsized_fn_params, transparent_unions)] #![no_core] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![expect(unused, improper_ctypes_definitions, internal_features)] // FIXME: some targets are broken in various ways. // Hence there are `cfg` throughout this test to disable parts of it on those targets. diff --git a/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs b/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs index 7f44ef9c68535..5b278f2506a24 100644 --- a/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs +++ b/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs @@ -1,8 +1,8 @@ // https://github.com/rust-lang/rust/issues/5754 //@ build-pass #![allow(dead_code)] -#![allow(improper_ctypes)] +#[repr(C)] struct TwoDoubles { r: f64, i: f64 diff --git a/tests/ui/abi/extern/extern-c-method-return-struct.rs b/tests/ui/abi/extern/extern-c-method-return-struct.rs index 679f0b37758ab..08cfe495ba2a3 100644 --- a/tests/ui/abi/extern/extern-c-method-return-struct.rs +++ b/tests/ui/abi/extern/extern-c-method-return-struct.rs @@ -2,13 +2,14 @@ //@ build-pass #![allow(dead_code)] + +#[repr(C)] pub struct Foo { x: isize, y: isize } impl Foo { - #[allow(improper_ctypes_definitions)] pub extern "C" fn foo_new() -> Foo { Foo { x: 21, y: 33 } } diff --git a/tests/ui/abi/extern/extern-pass-FiveU16s.rs b/tests/ui/abi/extern/extern-pass-FiveU16s.rs index 5f1307beb28e6..48edb8d4a5a92 100644 --- a/tests/ui/abi/extern/extern-pass-FiveU16s.rs +++ b/tests/ui/abi/extern/extern-pass-FiveU16s.rs @@ -1,5 +1,4 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct by value. @@ -8,6 +7,7 @@ // sizes, causing there to be padding in the last element. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct FiveU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-pass-TwoU16s.rs b/tests/ui/abi/extern/extern-pass-TwoU16s.rs index 8bde553050a40..47dd0674ae439 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU16s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU16s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-pass-TwoU32s.rs b/tests/ui/abi/extern/extern-pass-TwoU32s.rs index fc90eb6945c7e..f7d9459e8f82f 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU32s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU32s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU32s { one: u32, two: u32, diff --git a/tests/ui/abi/extern/extern-pass-TwoU64s.rs b/tests/ui/abi/extern/extern-pass-TwoU64s.rs index 603de2e49ab2d..dbce8690d78cd 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU64s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU64s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU64s { one: u64, two: u64, diff --git a/tests/ui/abi/extern/extern-pass-TwoU8s.rs b/tests/ui/abi/extern/extern-pass-TwoU8s.rs index a712d79a98dd2..56db34dcefea7 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU8s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU8s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU8s { one: u8, two: u8, diff --git a/tests/ui/abi/extern/extern-pass-empty.rs b/tests/ui/abi/extern/extern-pass-empty.rs index 1ad52b128ad93..707c1944cdd63 100644 --- a/tests/ui/abi/extern/extern-pass-empty.rs +++ b/tests/ui/abi/extern/extern-pass-empty.rs @@ -1,5 +1,5 @@ //@ run-pass -#![allow(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. +#![expect(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. // Test a foreign function that accepts empty struct. diff --git a/tests/ui/abi/extern/extern-return-FiveU16s.rs b/tests/ui/abi/extern/extern-return-FiveU16s.rs index d8ae8b2661c5a..d0566de1e6975 100644 --- a/tests/ui/abi/extern/extern-return-FiveU16s.rs +++ b/tests/ui/abi/extern/extern-return-FiveU16s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct FiveU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-return-TwoU16s.rs b/tests/ui/abi/extern/extern-return-TwoU16s.rs index bf909a8db24c7..601daf35b4ea9 100644 --- a/tests/ui/abi/extern/extern-return-TwoU16s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU16s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-return-TwoU32s.rs b/tests/ui/abi/extern/extern-return-TwoU32s.rs index c528da8cfc464..9f81286009959 100644 --- a/tests/ui/abi/extern/extern-return-TwoU32s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU32s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU32s { one: u32, two: u32, diff --git a/tests/ui/abi/extern/extern-return-TwoU64s.rs b/tests/ui/abi/extern/extern-return-TwoU64s.rs index d4f9540ec7b35..a7dd142ffbc0d 100644 --- a/tests/ui/abi/extern/extern-return-TwoU64s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU64s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU64s { one: u64, two: u64, diff --git a/tests/ui/abi/extern/extern-return-TwoU8s.rs b/tests/ui/abi/extern/extern-return-TwoU8s.rs index 228b27396249b..5f2d0418fd166 100644 --- a/tests/ui/abi/extern/extern-return-TwoU8s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU8s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU8s { one: u8, two: u8, diff --git a/tests/ui/abi/foreign/foreign-fn-with-byval.rs b/tests/ui/abi/foreign/foreign-fn-with-byval.rs index 9908ec2d2c01a..d5f87c1a804b5 100644 --- a/tests/ui/abi/foreign/foreign-fn-with-byval.rs +++ b/tests/ui/abi/foreign/foreign-fn-with-byval.rs @@ -1,7 +1,7 @@ //@ run-pass -#![allow(improper_ctypes, improper_ctypes_definitions)] #[derive(Copy, Clone)] +#[repr(C)] pub struct S { x: u64, y: u64, diff --git a/tests/ui/abi/issue-28676.rs b/tests/ui/abi/issue-28676.rs index 2abb4ce52b3b3..616ed6bde24b1 100644 --- a/tests/ui/abi/issue-28676.rs +++ b/tests/ui/abi/issue-28676.rs @@ -2,9 +2,9 @@ //@ ignore-backends: gcc #![allow(dead_code)] -#![allow(improper_ctypes)] #[derive(Copy, Clone)] +#[repr(C)] pub struct Quad { a: u64, b: u64, diff --git a/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs b/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs index 314db42280d99..578381dcd32d1 100644 --- a/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs +++ b/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs @@ -1,8 +1,8 @@ //@ run-pass #![allow(dead_code)] -#![allow(improper_ctypes)] #[derive(Copy, Clone)] +#[repr(C)] pub struct QuadFloats { a: f32, b: f32, diff --git a/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs b/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs index f694205174889..fa8ac0bfe594a 100644 --- a/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs +++ b/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs @@ -1,6 +1,4 @@ //@ run-pass -#![allow(dead_code)] -#![allow(improper_ctypes)] #[link(name = "rust_test_helpers", kind = "static")] extern "C" { diff --git a/tests/ui/abi/large-byval-align.rs b/tests/ui/abi/large-byval-align.rs index 69418e1cbc7b8..7e640fb42a3cb 100644 --- a/tests/ui/abi/large-byval-align.rs +++ b/tests/ui/abi/large-byval-align.rs @@ -3,10 +3,9 @@ //@ build-pass //@ ignore-backends: gcc -#[repr(align(536870912))] +#[repr(C, align(536870912))] pub struct A(i64); -#[allow(improper_ctypes_definitions)] pub extern "C" fn foo(x: A) {} fn main() { diff --git a/tests/ui/abi/non-rustic-unsized.rs b/tests/ui/abi/non-rustic-unsized.rs index d26c4af72ccaf..fa22f91afeb67 100644 --- a/tests/ui/abi/non-rustic-unsized.rs +++ b/tests/ui/abi/non-rustic-unsized.rs @@ -3,7 +3,7 @@ #![no_core] #![crate_type = "lib"] #![feature(no_core, unsized_fn_params)] -#![allow(improper_ctypes_definitions, improper_ctypes)] +#![expect(improper_ctypes_definitions, improper_ctypes)] extern crate minicore; use minicore::*; diff --git a/tests/ui/abi/simd-abi-checks-avx.rs b/tests/ui/abi/simd-abi-checks-avx.rs index 7432381d15b72..e47346d02ea59 100644 --- a/tests/ui/abi/simd-abi-checks-avx.rs +++ b/tests/ui/abi/simd-abi-checks-avx.rs @@ -4,7 +4,7 @@ #![feature(portable_simd)] #![feature(simd_ffi)] -#![allow(improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] use std::arch::x86_64::*; diff --git a/tests/ui/abi/simd-abi-checks-s390x.rs b/tests/ui/abi/simd-abi-checks-s390x.rs index 8ca3d2f457899..95028b8286d55 100644 --- a/tests/ui/abi/simd-abi-checks-s390x.rs +++ b/tests/ui/abi/simd-abi-checks-s390x.rs @@ -15,7 +15,7 @@ #![feature(no_core)] #![no_core] #![crate_type = "lib"] -#![allow(non_camel_case_types, improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] extern crate minicore; use minicore::simd::*; diff --git a/tests/ui/abi/simd-abi-checks-sse.rs b/tests/ui/abi/simd-abi-checks-sse.rs index 906b6ec65610e..4ed4b78166f9e 100644 --- a/tests/ui/abi/simd-abi-checks-sse.rs +++ b/tests/ui/abi/simd-abi-checks-sse.rs @@ -9,7 +9,7 @@ //@ dont-require-annotations: NOTE #![feature(no_core)] #![no_core] -#![allow(improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] extern crate minicore; use minicore::simd::Simd; diff --git a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs index 7d21307e1b2d9..f0f6bb3f765b2 100644 --- a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs +++ b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] #![feature(unsized_fn_params)] #![crate_type = "lib"] diff --git a/tests/ui/ffi/ffi-struct-size-alignment.rs b/tests/ui/ffi/ffi-struct-size-alignment.rs index 287ae7cad2b6d..00f25f0673c5c 100644 --- a/tests/ui/ffi/ffi-struct-size-alignment.rs +++ b/tests/ui/ffi/ffi-struct-size-alignment.rs @@ -1,12 +1,12 @@ //@ run-pass #![allow(dead_code)] -#![allow(improper_ctypes)] // Issue #3656 // Incorrect struct size computation in the FFI, because of not taking // the alignment of elements into account. use std::ffi::{c_uint, c_void}; +#[repr(C)] pub struct KEYGEN { hash_algorithm: [c_uint; 2], count: u32, From e7d3a6a9e68afaba5ea299e301c8e57be7626cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 10 Sep 2026 14:11:22 +0200 Subject: [PATCH 58/76] Move parse error recovery from some invalid expr ops out of line --- compiler/rustc_parse/src/diagnostics.rs | 4 +- compiler/rustc_parse/src/parser/expr.rs | 388 ++++++++---------- .../src/parser/expr/diagnostics.rs | 80 ++++ 3 files changed, 262 insertions(+), 210 deletions(-) create mode 100644 compiler/rustc_parse/src/parser/expr/diagnostics.rs diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 829fc5a600e8a..78240b0ee891b 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -797,7 +797,7 @@ pub(crate) struct EqFieldInit { #[derive(Diagnostic)] #[diag("unexpected token: `...`")] -pub(crate) struct DotDotDot { +pub(crate) struct DotDotDotExprOp { #[primary_span] #[suggestion( "use `..` for an exclusive range", @@ -816,7 +816,7 @@ pub(crate) struct DotDotDot { #[derive(Diagnostic)] #[diag("unexpected token: `<-`")] -pub(crate) struct LeftArrowOperator { +pub(crate) struct LArrowExprOp { #[primary_span] #[suggestion( "if you meant to write a comparison against a negative value, add a space in between `<` and `-`", diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 8238a6518e41d..a7bcb93d5b084 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -34,8 +34,9 @@ use super::{ AttrWrapper, BlockMode, ClosureSpans, ExpTokenPair, ForceCollect, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenType, Trailing, UsePreAttrPos, }; -use crate::diagnostics::ExprParenthesesNeeded; -use crate::{diagnostics, exp, maybe_recover_from_interpolated_ty_qpath}; +use crate::{exp, maybe_recover_from_interpolated_ty_qpath}; + +mod diagnostics; #[derive(Debug)] pub(super) enum DestructuredFloat { @@ -165,74 +166,22 @@ impl<'a> Parser<'a> { } { break; } - // Check for deprecated `...` syntax - if self.token == token::DotDotDot && op.node == AssocOp::Range(RangeLimits::Closed) { - self.err_dotdotdot_syntax(self.token.span); - } - if self.token == token::LArrow { - self.err_larrow_operator(self.token.span); - } + self.reject_dotdotdot_expr_op(); + self.reject_larrow_expr_op(); parsed_something = true; self.bump(); - if op.node.is_comparison() { - if let Some(expr) = self.check_no_chained_comparison(&lhs, &op)? { - return Ok((expr, parsed_something)); - } - } - // Look for JS' `===` and `!==` and recover - if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node - && self.token == token::Eq - && self.prev_token.span.hi() == self.token.span.lo() + if op.node.is_comparison() + && let Some(expr) = self.check_no_chained_comparison(&lhs, &op)? { - let sp = op.span.to(self.token.span); - let sugg = bop.as_str().into(); - let invalid = format!("{sugg}="); - self.dcx().emit_err(diagnostics::InvalidComparisonOperator { - span: sp, - invalid: invalid.clone(), - sub: diagnostics::InvalidComparisonOperatorSub::Correctable { - span: sp, - invalid, - correct: sugg, - }, - }); - self.bump(); + return Ok((expr, parsed_something)); } - // Look for PHP's `<>` and recover - if op.node == AssocOp::Binary(BinOpKind::Lt) - && self.token == token::Gt - && self.prev_token.span.hi() == self.token.span.lo() - { - let sp = op.span.to(self.token.span); - self.dcx().emit_err(diagnostics::InvalidComparisonOperator { - span: sp, - invalid: "<>".into(), - sub: diagnostics::InvalidComparisonOperatorSub::Correctable { - span: sp, - invalid: "<>".into(), - correct: "!=".into(), - }, - }); - self.bump(); - } - - // Look for C++'s `<=>` and recover - if op.node == AssocOp::Binary(BinOpKind::Le) - && self.token == token::Gt - && self.prev_token.span.hi() == self.token.span.lo() - { - let sp = op.span.to(self.token.span); - self.dcx().emit_err(diagnostics::InvalidComparisonOperator { - span: sp, - invalid: "<=>".into(), - sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp), - }); - self.bump(); - } + self.recover_from_strict_eq_op(op); + self.recover_from_diamond_ne_op(op); + self.recover_from_spaceship_cmp_op(op); if self.prev_token == token::Plus && self.token == token::Plus @@ -337,10 +286,10 @@ impl<'a> Parser<'a> { /// but the next token implies this should be parsed as an expression. /// For example: `if let Some(x) = x { x } else { 0 } / 2`. fn error_found_expr_would_be_stmt(&self, lhs: &Expr) { - self.dcx().emit_err(diagnostics::FoundExprWouldBeStmt { + self.dcx().emit_err(crate::diagnostics::FoundExprWouldBeStmt { span: self.token.span, token: pprust::token_to_string(&self.token), - suggestion: ExprParenthesesNeeded::surrounding(lhs.span), + suggestion: crate::diagnostics::ExprParenthesesNeeded::surrounding(lhs.span), }); } @@ -377,18 +326,22 @@ impl<'a> Parser<'a> { (None, Some((Ident { name: sym::and, span }, IdentIsRaw::No))) if self.may_recover() => { - self.dcx().emit_err(diagnostics::InvalidLogicalOperator { + self.dcx().emit_err(crate::diagnostics::InvalidLogicalOperator { span: self.token.span, incorrect: "and".into(), - sub: diagnostics::InvalidLogicalOperatorSub::Conjunction(self.token.span), + sub: crate::diagnostics::InvalidLogicalOperatorSub::Conjunction( + self.token.span, + ), }); (AssocOp::Binary(BinOpKind::And), span) } (None, Some((Ident { name: sym::or, span }, IdentIsRaw::No))) if self.may_recover() => { - self.dcx().emit_err(diagnostics::InvalidLogicalOperator { + self.dcx().emit_err(crate::diagnostics::InvalidLogicalOperator { span: self.token.span, incorrect: "or".into(), - sub: diagnostics::InvalidLogicalOperatorSub::Disjunction(self.token.span), + sub: crate::diagnostics::InvalidLogicalOperatorSub::Disjunction( + self.token.span, + ), }); (AssocOp::Binary(BinOpKind::Or), span) } @@ -441,14 +394,11 @@ impl<'a> Parser<'a> { /// Parses prefix-forms of range notation: `..expr`, `..`, `..=expr`. fn parse_expr_prefix_range(&mut self, attrs: AttrWrapper) -> PResult<'a, Box> { if !attrs.is_empty() { - let err = diagnostics::DotDotRangeAttribute { span: self.token.span }; + let err = crate::diagnostics::DotDotRangeAttribute { span: self.token.span }; self.dcx().emit_err(err); } - // Check for deprecated `...` syntax. - if self.token == token::DotDotDot { - self.err_dotdotdot_syntax(self.token.span); - } + self.reject_dotdotdot_expr_op(); debug_assert!( self.token.is_range_separator(), @@ -513,7 +463,7 @@ impl<'a> Parser<'a> { } // `+lit` token::Plus if this.look_ahead(1, |tok| tok.is_numeric_lit()) => { - let mut err = diagnostics::LeadingPlusNotSupported { + let mut err = crate::diagnostics::LeadingPlusNotSupported { span: lo, remove_plus: None, add_parentheses: None, @@ -521,7 +471,8 @@ impl<'a> Parser<'a> { // a block on the LHS might have been intended to be an expression instead if let Some(sp) = this.psess.ambiguous_block_expr_parse.borrow().get(&lo) { - err.add_parentheses = Some(ExprParenthesesNeeded::surrounding(*sp)); + err.add_parentheses = + Some(crate::diagnostics::ExprParenthesesNeeded::surrounding(*sp)); } else { err.remove_plus = Some(lo); } @@ -574,7 +525,7 @@ impl<'a> Parser<'a> { /// Recover on `~expr` in favor of `!expr`. fn recover_tilde_expr(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> { - self.dcx().emit_err(diagnostics::TildeAsUnaryOperator(lo)); + self.dcx().emit_err(crate::diagnostics::TildeAsUnaryOperator(lo)); self.parse_expr_unary(lo, UnOp::Not) } @@ -605,14 +556,14 @@ impl<'a> Parser<'a> { let negated_token = self.look_ahead(1, |t| *t); let sub_diag = if negated_token.is_numeric_lit() { - diagnostics::NotAsNegationOperatorSub::SuggestNotBitwise + crate::diagnostics::NotAsNegationOperatorSub::SuggestNotBitwise } else if negated_token.is_bool_lit() { - diagnostics::NotAsNegationOperatorSub::SuggestNotLogical + crate::diagnostics::NotAsNegationOperatorSub::SuggestNotLogical } else { - diagnostics::NotAsNegationOperatorSub::SuggestNotDefault + crate::diagnostics::NotAsNegationOperatorSub::SuggestNotDefault }; - self.dcx().emit_err(diagnostics::NotAsNegationOperator { + self.dcx().emit_err(crate::diagnostics::NotAsNegationOperator { negated: negated_token.span, negated_desc: super::token_descr(&negated_token), // Span the `not` plus trailing whitespace to avoid @@ -683,7 +634,7 @@ impl<'a> Parser<'a> { match self.parse_expr_labeled(label, false) { Ok(expr) => { type_err.cancel(); - self.dcx().emit_err(diagnostics::MalformedLoopLabel { + self.dcx().emit_err(crate::diagnostics::MalformedLoopLabel { span: label.ident.span, suggestion: label.ident.span.shrink_to_lo(), }); @@ -709,23 +660,24 @@ impl<'a> Parser<'a> { let args_span = self.look_ahead(1, |t| t.span).to(span_after_type); match self.token.kind { - token::Lt => { - self.dcx().emit_err(diagnostics::ComparisonInterpretedAsGeneric { + token::Lt => self.dcx().emit_err( + crate::diagnostics::ComparisonInterpretedAsGeneric { comparison: self.token.span, r#type: pprust::path_to_string(&path), args: args_span, - suggestion: diagnostics::ComparisonInterpretedAsGenericSugg { - left: expr.span.shrink_to_lo(), - right: expr.span.shrink_to_hi(), - }, - }) - } + suggestion: + crate::diagnostics::ComparisonInterpretedAsGenericSugg { + left: expr.span.shrink_to_lo(), + right: expr.span.shrink_to_hi(), + }, + }, + ), token::Shl => { - self.dcx().emit_err(diagnostics::ShiftInterpretedAsGeneric { + self.dcx().emit_err(crate::diagnostics::ShiftInterpretedAsGeneric { shift: self.token.span, r#type: pprust::path_to_string(&path), args: args_span, - suggestion: diagnostics::ShiftInterpretedAsGenericSugg { + suggestion: crate::diagnostics::ShiftInterpretedAsGenericSugg { left: expr.span.shrink_to_lo(), right: expr.span.shrink_to_hi(), }, @@ -835,8 +787,10 @@ impl<'a> Parser<'a> { } fn error_remove_borrow_lifetime(&self, span: Span, lt_span: Span) { - self.dcx() - .emit_err(diagnostics::LifetimeInBorrowExpression { span, lifetime_span: lt_span }); + self.dcx().emit_err(crate::diagnostics::LifetimeInBorrowExpression { + span, + lifetime_span: lt_span, + }); } /// Parse `mut?` or `[ raw | pin ] [ const | mut ]`. @@ -895,7 +849,7 @@ impl<'a> Parser<'a> { // Recovery for `expr->suffix`. self.bump(); let span = self.prev_token.span; - self.dcx().emit_err(diagnostics::ExprRArrowCall { span }); + self.dcx().emit_err(crate::diagnostics::ExprRArrowCall { span }); true } else { self.eat(exp!(Dot)) @@ -1018,7 +972,7 @@ impl<'a> Parser<'a> { } _ => (span, actual), }; - self.dcx().emit_err(diagnostics::UnexpectedTokenAfterDot { span, actual }); + self.dcx().emit_err(crate::diagnostics::UnexpectedTokenAfterDot { span, actual }); } /// We need an identifier or integer, but the next token is a float. @@ -1135,7 +1089,7 @@ impl<'a> Parser<'a> { // Parse this both to give helpful error messages and to // verify it can be done with this parser setup. ExprKind::Index(ref left, ref _right, span) => { - self.dcx().emit_err(diagnostics::ArrayIndexInOffsetOf(span)); + self.dcx().emit_err(crate::diagnostics::ArrayIndexInOffsetOf(span)); current = left; } ExprKind::Lit(token::Lit { @@ -1144,10 +1098,12 @@ impl<'a> Parser<'a> { suffix, }) => { if let Some(suffix) = suffix { - self.dcx().emit_err(diagnostics::InvalidLiteralSuffixOnTupleIndex { - span: current.span, - suffix, - }); + self.dcx().emit_err( + crate::diagnostics::InvalidLiteralSuffixOnTupleIndex { + span: current.span, + suffix, + }, + ); } match self.break_up_float(symbol, current.span) { // 1e2 @@ -1187,14 +1143,15 @@ impl<'a> Parser<'a> { fields.insert(start_idx, *ident) } _ => { - self.dcx().emit_err(diagnostics::InvalidOffsetOf(current.span)); + self.dcx() + .emit_err(crate::diagnostics::InvalidOffsetOf(current.span)); break; } } break; } _ => { - self.dcx().emit_err(diagnostics::InvalidOffsetOf(current.span)); + self.dcx().emit_err(crate::diagnostics::InvalidOffsetOf(current.span)); break; } } @@ -1204,12 +1161,12 @@ impl<'a> Parser<'a> { break; } else if trailing_dot.is_none() { // This loop should only repeat if there is a trailing dot. - self.dcx().emit_err(diagnostics::InvalidOffsetOf(self.token.span)); + self.dcx().emit_err(crate::diagnostics::InvalidOffsetOf(self.token.span)); break; } } if let Some(dot) = trailing_dot { - self.dcx().emit_err(diagnostics::InvalidOffsetOf(dot)); + self.dcx().emit_err(crate::diagnostics::InvalidOffsetOf(dot)); } Ok(fields.into_iter().collect()) } @@ -1223,7 +1180,7 @@ impl<'a> Parser<'a> { suffix: Option, ) -> Box { if let Some(suffix) = suffix { - self.dcx().emit_err(diagnostics::InvalidLiteralSuffixOnTupleIndex { + self.dcx().emit_err(crate::diagnostics::InvalidLiteralSuffixOnTupleIndex { span: ident_span, suffix, }); @@ -1310,14 +1267,14 @@ impl<'a> Parser<'a> { err.cancel(); let type_str = pprust::path_to_string(&path); self.dcx() - .create_err(diagnostics::ParenthesesWithStructFields { + .create_err(crate::diagnostics::ParenthesesWithStructFields { span, - braces_for_struct: diagnostics::BracesForStructLiteral { + braces_for_struct: crate::diagnostics::BracesForStructLiteral { first: open_paren, second: close_paren, r#type: type_str.clone(), }, - no_fields_for_fn: diagnostics::NoFieldsForFnCall { + no_fields_for_fn: crate::diagnostics::NoFieldsForFnCall { r#type: type_str, fields: fields .into_iter() @@ -1419,7 +1376,7 @@ impl<'a> Parser<'a> { if let Some(args) = seg.args { // See `StashKey::GenericInFieldExpr` for more info on why we stash this. self.dcx() - .create_err(diagnostics::FieldExpressionWithGeneric(args.span())) + .create_err(crate::diagnostics::FieldExpressionWithGeneric(args.span())) .stash(seg.ident.span, StashKey::GenericInFieldExpr); } @@ -1491,7 +1448,9 @@ impl<'a> Parser<'a> { // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }` // then suggest parens around the lhs. if let Some(sp) = this.psess.ambiguous_block_expr_parse.borrow().get(&lo) { - err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + err.subdiagnostic(crate::diagnostics::ExprParenthesesNeeded::surrounding( + *sp, + )); } err }) @@ -1689,7 +1648,8 @@ impl<'a> Parser<'a> { let (span, kind) = if self.eat(exp!(Bang)) { // MACRO INVOCATION expression if qself.is_some() { - self.dcx().emit_err(diagnostics::MacroInvocationWithQualifiedPath(path.span)); + self.dcx() + .emit_err(crate::diagnostics::MacroInvocationWithQualifiedPath(path.span)); } let lo = path.span; let mac = Box::new(MacCall { path, args: self.parse_delim_args()? }); @@ -1734,7 +1694,7 @@ impl<'a> Parser<'a> { { let (lit, _) = self.recover_unclosed_char(label_.ident, Parser::mk_token_lit_char, |self_| { - self_.dcx().create_err(diagnostics::UnexpectedTokenAfterLabel { + self_.dcx().create_err(crate::diagnostics::UnexpectedTokenAfterLabel { span: self_.token.span, remove_label: None, enclose_in_block: None, @@ -1746,7 +1706,7 @@ impl<'a> Parser<'a> { && (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt)) { // We're probably inside of a `Path<'a>` that needs a turbofish - let guar = self.dcx().emit_err(diagnostics::UnexpectedTokenAfterLabel { + let guar = self.dcx().emit_err(crate::diagnostics::UnexpectedTokenAfterLabel { span: self.token.span, remove_label: None, enclose_in_block: None, @@ -1754,7 +1714,7 @@ impl<'a> Parser<'a> { consume_colon = false; Ok(self.mk_expr_err(lo, guar)) } else { - let mut err = diagnostics::UnexpectedTokenAfterLabel { + let mut err = crate::diagnostics::UnexpectedTokenAfterLabel { span: self.token.span, remove_label: None, enclose_in_block: None, @@ -1791,7 +1751,7 @@ impl<'a> Parser<'a> { return expr; } - err.enclose_in_block = Some(diagnostics::UnexpectedTokenAfterLabelSugg { + err.enclose_in_block = Some(crate::diagnostics::UnexpectedTokenAfterLabelSugg { left: span.shrink_to_lo(), right: span.shrink_to_hi(), }); @@ -1807,7 +1767,7 @@ impl<'a> Parser<'a> { }?; if !ate_colon && consume_colon { - self.dcx().emit_err(diagnostics::RequireColonAfterLabeledExpression { + self.dcx().emit_err(crate::diagnostics::RequireColonAfterLabeledExpression { span: expr.span, label: lo, label_end: lo.between(tok_sp), @@ -1856,7 +1816,7 @@ impl<'a> Parser<'a> { self.bump(); // `catch` let span = lo.to(self.prev_token.span); - self.dcx().emit_err(diagnostics::DoCatchSyntaxRemoved { span }); + self.dcx().emit_err(crate::diagnostics::DoCatchSyntaxRemoved { span }); self.parse_try_block(lo) } @@ -1916,9 +1876,9 @@ impl<'a> Parser<'a> { // The value expression can be a labeled loop, see issue #86948, e.g.: // `loop { break 'label: loop { break 'label 42; }; }` let lexpr = self.parse_expr_labeled(label, true)?; - self.dcx().emit_err(diagnostics::LabeledLoopInBreak { + self.dcx().emit_err(crate::diagnostics::LabeledLoopInBreak { span: lexpr.span, - sub: diagnostics::WrapInParentheses::Expression { + sub: crate::diagnostics::WrapInParentheses::Expression { left: lexpr.span.shrink_to_lo(), right: lexpr.span.shrink_to_hi(), }, @@ -1945,8 +1905,8 @@ impl<'a> Parser<'a> { BREAK_WITH_LABEL_AND_LOOP, lo.to(expr.span), ast::CRATE_NODE_ID, - diagnostics::BreakWithLabelAndLoop { - sub: diagnostics::BreakWithLabelAndLoopSub { + crate::diagnostics::BreakWithLabelAndLoop { + sub: crate::diagnostics::BreakWithLabelAndLoopSub { left: span.shrink_to_lo(), right: span.shrink_to_hi(), }, @@ -2028,8 +1988,9 @@ impl<'a> Parser<'a> { self.bump(); // `#` let Some((ident, IdentIsRaw::No)) = self.token.ident() else { - let err = - self.dcx().create_err(diagnostics::ExpectedBuiltinIdent { span: self.token.span }); + let err = self + .dcx() + .create_err(crate::diagnostics::ExpectedBuiltinIdent { span: self.token.span }); return Err(err); }; self.psess.gated_spans.gate(sym::builtin_syntax, ident.span); @@ -2039,7 +2000,7 @@ impl<'a> Parser<'a> { let ret = if let Some(res) = parse(self, lo, ident)? { Ok(res) } else { - let err = self.dcx().create_err(diagnostics::UnknownBuiltinConstruct { + let err = self.dcx().create_err(crate::diagnostics::UnknownBuiltinConstruct { span: lo.to(ident.span), name: ident, }); @@ -2188,7 +2149,7 @@ impl<'a> Parser<'a> { } }); if let Some(recovered) = recovered { - self.dcx().emit_err(diagnostics::FloatLiteralRequiresIntegerPart { + self.dcx().emit_err(crate::diagnostics::FloatLiteralRequiresIntegerPart { span: recovered.span, suggestion: recovered.span.shrink_to_lo(), }); @@ -2322,9 +2283,9 @@ impl<'a> Parser<'a> { let mut snapshot = self.create_snapshot_for_diagnostic(); match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) { Ok(arr) => { - let guar = self.dcx().emit_err(diagnostics::ArrayBracketsInsteadOfBraces { + let guar = self.dcx().emit_err(crate::diagnostics::ArrayBracketsInsteadOfBraces { span: arr.span, - sub: diagnostics::ArrayBracketsInsteadOfBracesSugg { + sub: crate::diagnostics::ArrayBracketsInsteadOfBracesSugg { left: lo, right: snapshot.prev_token.span, }, @@ -2370,7 +2331,7 @@ impl<'a> Parser<'a> { .span_to_snippet(snapshot.token.span) .is_ok_and(|snippet| snippet == "]") => { - return Err(self.dcx().create_err(diagnostics::MissingSemicolonBeforeArray { + return Err(self.dcx().create_err(crate::diagnostics::MissingSemicolonBeforeArray { open_delim: open_delim_span, semicolon: prev_span.shrink_to_hi(), })); @@ -2396,10 +2357,10 @@ impl<'a> Parser<'a> { } if self.token.is_metavar_block() { - self.dcx().emit_err(diagnostics::InvalidBlockMacroSegment { + self.dcx().emit_err(crate::diagnostics::InvalidBlockMacroSegment { span: self.token.span, context: lo.to(self.token.span), - wrap: diagnostics::WrapInExplicitBlock { + wrap: crate::diagnostics::WrapInExplicitBlock { lo: self.token.span.shrink_to_lo(), hi: self.token.span.shrink_to_hi(), }, @@ -2571,9 +2532,9 @@ impl<'a> Parser<'a> { // Check for `move async` and recover if self.check_keyword(exp!(Async)) { let move_async_span = self.token.span.with_lo(self.prev_token.span.data().lo); - Err(self - .dcx() - .create_err(diagnostics::AsyncMoveOrderIncorrect { span: move_async_span })) + Err(self.dcx().create_err(crate::diagnostics::AsyncMoveOrderIncorrect { + span: move_async_span, + })) } else { Ok(CaptureBy::Value { move_kw: move_kw_span }) } @@ -2583,9 +2544,9 @@ impl<'a> Parser<'a> { // Check for `use async` and recover if self.check_keyword(exp!(Async)) { let use_async_span = self.token.span.with_lo(self.prev_token.span.data().lo); - Err(self - .dcx() - .create_err(diagnostics::AsyncUseOrderIncorrect { span: use_async_span })) + Err(self.dcx().create_err(crate::diagnostics::AsyncUseOrderIncorrect { + span: use_async_span, + })) } else { Ok(CaptureBy::Use { use_kw: use_kw_span }) } @@ -2667,10 +2628,10 @@ impl<'a> Parser<'a> { ExprKind::Binary(Spanned { span: binop_span, .. }, _, right) if let ExprKind::Block(_, None) = right.kind => { - let guar = this.dcx().emit_err(diagnostics::IfExpressionMissingThenBlock { + let guar = this.dcx().emit_err(crate::diagnostics::IfExpressionMissingThenBlock { if_span: lo, missing_then_block_sub: - diagnostics::IfExpressionMissingThenBlockSub::UnfinishedCondition( + crate::diagnostics::IfExpressionMissingThenBlockSub::UnfinishedCondition( cond_span.shrink_to_lo().to(*binop_span), ), let_else_sub: None, @@ -2678,10 +2639,11 @@ impl<'a> Parser<'a> { std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi(), guar)) } ExprKind::Block(_, None) => { - let guar = this.dcx().emit_err(diagnostics::IfExpressionMissingCondition { - if_span: lo.with_neighbor(cond.span).shrink_to_hi(), - block_span: self.psess.source_map().start_point(cond_span), - }); + let guar = + this.dcx().emit_err(crate::diagnostics::IfExpressionMissingCondition { + if_span: lo.with_neighbor(cond.span).shrink_to_hi(), + block_span: self.psess.source_map().start_point(cond_span), + }); std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi(), guar)) } _ => { @@ -2699,13 +2661,14 @@ impl<'a> Parser<'a> { if let Some(block) = recover_block_from_condition(self) { block } else { - let let_else_sub = matches!(cond.kind, ExprKind::Let(..)) - .then(|| diagnostics::IfExpressionLetSomeSub { if_span: lo.until(cond_span) }); + let let_else_sub = matches!(cond.kind, ExprKind::Let(..)).then(|| { + crate::diagnostics::IfExpressionLetSomeSub { if_span: lo.until(cond_span) } + }); - let guar = self.dcx().emit_err(diagnostics::IfExpressionMissingThenBlock { + let guar = self.dcx().emit_err(crate::diagnostics::IfExpressionMissingThenBlock { if_span: lo, missing_then_block_sub: - diagnostics::IfExpressionMissingThenBlockSub::AddThenBlock( + crate::diagnostics::IfExpressionMissingThenBlockSub::AddThenBlock( cond_span.shrink_to_hi(), ), let_else_sub, @@ -2798,9 +2761,9 @@ impl<'a> Parser<'a> { /// Parses a `let $pat = $expr` pseudo-expression. fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, Box> { let recovered: Recovered = if !restrictions.contains(Restrictions::ALLOW_LET) { - let err = diagnostics::ExpectedExpressionFoundLet { + let err = crate::diagnostics::ExpectedExpressionFoundLet { span: self.token.span, - reason: diagnostics::ForbiddenLetReason::OtherForbidden, + reason: crate::diagnostics::ForbiddenLetReason::OtherForbidden, missing_let: None, comparison: None, }; @@ -2822,7 +2785,7 @@ impl<'a> Parser<'a> { CommaRecoveryMode::LikelyTuple, )?; if self.token == token::EqEq { - self.dcx().emit_err(diagnostics::ExpectedEqForLetExpr { + self.dcx().emit_err(crate::diagnostics::ExpectedEqForLetExpr { span: self.token.span, sugg_span: self.token.span, }); @@ -2888,7 +2851,7 @@ impl<'a> Parser<'a> { || matches!(cond.kind, ExprKind::MacCall(..))) => { - self.dcx().emit_err(diagnostics::ExpectedElseBlock { + self.dcx().emit_err(crate::diagnostics::ExpectedElseBlock { first_tok_span, first_tok, else_span, @@ -2924,7 +2887,7 @@ impl<'a> Parser<'a> { let attributes = x0.span.until(branch_span); let last = xn.span; let ctx = if is_ctx_else { "else" } else { "if" }; - self.dcx().emit_err(diagnostics::OuterAttributeNotAllowedOnIfElse { + self.dcx().emit_err(crate::diagnostics::OuterAttributeNotAllowedOnIfElse { last, branch_span, ctx_span, @@ -2939,7 +2902,7 @@ impl<'a> Parser<'a> { && let BinOpKind::And = binop && let ExprKind::If(cond, ..) = &right.kind { - Err(self.dcx().create_err(diagnostics::UnexpectedIfWithIf( + Err(self.dcx().create_err(crate::diagnostics::UnexpectedIfWithIf( binop_span.shrink_to_hi().to(cond.span.shrink_to_lo()), ))) } else { @@ -2989,12 +2952,12 @@ impl<'a> Parser<'a> { let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span)); self.bump(); // ) err.cancel(); - self.dcx().emit_err(diagnostics::ParenthesesInForHead { + self.dcx().emit_err(crate::diagnostics::ParenthesesInForHead { span, // With e.g. `for (x) in y)` this would replace `(x) in y)` // with `x) in y)` which is syntactically invalid. // However, this is prevented before we get here. - sugg: diagnostics::ParenthesesInForHeadSugg { left, right }, + sugg: crate::diagnostics::ParenthesesInForHeadSugg { left, right }, }); Ok((self.mk_pat(start_span.to(right), ast::PatKind::Wild), expr)) } else { @@ -3029,7 +2992,7 @@ impl<'a> Parser<'a> { && self.token.kind != token::OpenBrace && self.may_recover() { - let guar = self.dcx().emit_err(diagnostics::MissingExpressionInForLoop { + let guar = self.dcx().emit_err(crate::diagnostics::MissingExpressionInForLoop { span: expr.span.shrink_to_lo(), }); let err_expr = self.mk_expr(expr.span, ExprKind::Err(guar)); @@ -3071,7 +3034,7 @@ impl<'a> Parser<'a> { let else_span = self.token.span; self.bump(); let else_clause = self.parse_expr_else()?; - self.dcx().emit_err(diagnostics::LoopElseNotSupported { + self.dcx().emit_err(crate::diagnostics::LoopElseNotSupported { span: else_span.to(else_clause.span), loop_kind, loop_kw, @@ -3085,18 +3048,18 @@ impl<'a> Parser<'a> { // Possibly using JS syntax (#75311). let span = self.token.span; self.bump(); - (span, Some(diagnostics::MissingInInForLoopSub::InNotOf(span))) + (span, Some(crate::diagnostics::MissingInInForLoopSub::InNotOf(span))) } else if self.eat(exp!(Eq)) { let span = self.prev_token.span; - (span, Some(diagnostics::MissingInInForLoopSub::InNotEq(span))) + (span, Some(crate::diagnostics::MissingInInForLoopSub::InNotEq(span))) } else { let span = self.prev_token.span.between(self.token.span); let sub = (!self.for_loop_head_has_in()) - .then_some(diagnostics::MissingInInForLoopSub::AddIn(span)); + .then_some(crate::diagnostics::MissingInInForLoopSub::AddIn(span)); (span, sub) }; - self.dcx().emit_err(diagnostics::MissingInInForLoop { span, sub }); + self.dcx().emit_err(crate::diagnostics::MissingInInForLoop { span, sub }); } /// Whether the `for` loop header already contains an `in` before its body. @@ -3166,7 +3129,7 @@ impl<'a> Parser<'a> { if let Some((ident, is_raw)) = self.token.lifetime() { // Disallow `'fn`, but with a better error message than `expect_lifetime`. if is_raw == IdentIsRaw::No && ident.without_first_quote().is_reserved() { - self.dcx().emit_err(diagnostics::KeywordLabel { span: ident.span }); + self.dcx().emit_err(crate::diagnostics::KeywordLabel { span: ident.span }); } self.bump(); @@ -3263,18 +3226,20 @@ impl<'a> Parser<'a> { let err = |this: &Parser<'_>, stmts: Vec| { let span = stmts[0].span.to(stmts[stmts.len() - 1].span); - let guar = this.dcx().emit_err(diagnostics::MatchArmBodyWithoutBraces { + let guar = this.dcx().emit_err(crate::diagnostics::MatchArmBodyWithoutBraces { statements: span, arrow: arrow_span, num_statements: stmts.len(), sub: if stmts.len() > 1 { - diagnostics::MatchArmBodyWithoutBracesSugg::AddBraces { + crate::diagnostics::MatchArmBodyWithoutBracesSugg::AddBraces { left: span.shrink_to_lo(), right: span.shrink_to_hi(), num_statements: stmts.len(), } } else { - diagnostics::MatchArmBodyWithoutBracesSugg::UseComma { semicolon: semi_sp } + crate::diagnostics::MatchArmBodyWithoutBracesSugg::UseComma { + semicolon: semi_sp, + } }, }); (span, guar) @@ -3492,7 +3457,7 @@ impl<'a> Parser<'a> { .is_ok(); if pattern_follows && snapshot.check(exp!(FatArrow)) { err.cancel(); - let guar = this.dcx().emit_err(diagnostics::MissingCommaAfterMatchArm { + let guar = this.dcx().emit_err(crate::diagnostics::MissingCommaAfterMatchArm { span: arm_span.shrink_to_hi(), }); return Ok(Recovered::Yes(guar)); @@ -3585,9 +3550,9 @@ impl<'a> Parser<'a> { checker.visit_expr(&mut guard.cond); let right = self.prev_token.span; - self.dcx().emit_err(diagnostics::ParenthesesInMatchPat { + self.dcx().emit_err(crate::diagnostics::ParenthesesInMatchPat { span: vec![left, right], - sugg: diagnostics::ParenthesesInMatchPatSugg { left, right }, + sugg: crate::diagnostics::ParenthesesInMatchPatSugg { left, right }, }); if let Some(guar) = checker.found_incorrect_let_chain { @@ -3664,7 +3629,9 @@ impl<'a> Parser<'a> { let (attrs, body) = self.parse_inner_attrs_and_block(None)?; if self.eat_keyword(exp!(Catch)) { - Err(self.dcx().create_err(diagnostics::CatchAfterTry { span: self.prev_token.span })) + Err(self + .dcx() + .create_err(crate::diagnostics::CatchAfterTry { span: self.prev_token.span })) } else { let span = span_lo.to(body.span); let gate_sym = @@ -3767,9 +3734,9 @@ impl<'a> Parser<'a> { match self.parse_expr_struct(qself.clone(), path.clone(), false) { Ok(expr) => { // This is a struct literal, but we don't accept them here. - self.dcx().emit_err(diagnostics::StructLiteralNotAllowedHere { + self.dcx().emit_err(crate::diagnostics::StructLiteralNotAllowedHere { span: expr.span, - sub: diagnostics::StructLiteralNotAllowedHereSugg { + sub: crate::diagnostics::StructLiteralNotAllowedHereSugg { left: path.span.shrink_to_lo(), right: expr.span.shrink_to_hi(), }, @@ -3811,10 +3778,12 @@ impl<'a> Parser<'a> { )?; let guar = if is_underscore_entry_point { - self.dcx().create_err(diagnostics::StructLiteralPlaceholderPath { span }).emit() + self.dcx() + .create_err(crate::diagnostics::StructLiteralPlaceholderPath { span }) + .emit() } else { self.dcx() - .create_err(diagnostics::StructLiteralWithoutPathLate { + .create_err(crate::diagnostics::StructLiteralWithoutPathLate { span: expr.span, suggestion_span: expr.span.shrink_to_lo(), }) @@ -3846,8 +3815,8 @@ impl<'a> Parser<'a> { let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD); let async_block_err = |e: &mut Diag<'_>, span: Span| { - diagnostics::AsyncBlockIn2015 { span }.add_to_diag(e); - diagnostics::HelpUseLatestEdition::new().add_to_diag(e); + crate::diagnostics::AsyncBlockIn2015 { span }.add_to_diag(e); + crate::diagnostics::HelpUseLatestEdition::new().add_to_diag(e); }; while self.token != close.tok { @@ -4029,7 +3998,7 @@ impl<'a> Parser<'a> { if self.token != token::Comma { return; } - self.dcx().emit_err(diagnostics::CommaAfterBaseStruct { + self.dcx().emit_err(crate::diagnostics::CommaAfterBaseStruct { span: span.to(self.prev_token.span), comma: self.token.span, }); @@ -4040,7 +4009,8 @@ impl<'a> Parser<'a> { if !self.look_ahead(1, |t| t == close) && self.eat(exp!(DotDotDot)) { // recover from typo of `...`, suggest `..` let span = self.prev_token.span; - self.dcx().emit_err(diagnostics::MissingDotDot { token_span: span, sugg_span: span }); + self.dcx() + .emit_err(crate::diagnostics::MissingDotDot { token_span: span, sugg_span: span }); return true; } false @@ -4053,7 +4023,7 @@ impl<'a> Parser<'a> { let label = format!("'{}", ident.name); let ident = Ident::new(Symbol::intern(&label), ident.span); - self.dcx().emit_err(diagnostics::ExpectedLabelFoundIdent { + self.dcx().emit_err(crate::diagnostics::ExpectedLabelFoundIdent { span: ident.span, start: ident.span.shrink_to_lo(), }); @@ -4080,7 +4050,7 @@ impl<'a> Parser<'a> { || t == &token::CloseParen }); if is_wrong { - return Err(this.dcx().create_err(diagnostics::ExpectedStructField { + return Err(this.dcx().create_err(crate::diagnostics::ExpectedStructField { span: this.look_ahead(1, |t| t.span), ident_span: this.token.span, token: pprust::token_to_string(&this.look_ahead(1, |t| *t)), @@ -4121,20 +4091,12 @@ impl<'a> Parser<'a> { return; } - self.dcx().emit_err(diagnostics::EqFieldInit { + self.dcx().emit_err(crate::diagnostics::EqFieldInit { span: self.token.span, eq: field_name.span.shrink_to_hi().to(self.token.span), }); } - fn err_dotdotdot_syntax(&self, span: Span) { - self.dcx().emit_err(diagnostics::DotDotDot { span }); - } - - fn err_larrow_operator(&self, span: Span) { - self.dcx().emit_err(diagnostics::LeftArrowOperator { span }); - } - fn mk_assign_op(&self, assign_op: AssignOp, lhs: Box, rhs: Box) -> ExprKind { ExprKind::AssignOp(assign_op, lhs, rhs) } @@ -4282,9 +4244,9 @@ struct CondChecker<'a> { parser: &'a Parser<'a>, let_chains_policy: LetChainsPolicy, depth: u32, - forbid_let_reason: Option, - missing_let: Option, - comparison: Option, + forbid_let_reason: Option, + missing_let: Option, + comparison: Option, found_incorrect_let_chain: Option, } @@ -4311,12 +4273,13 @@ impl MutVisitor for CondChecker<'_> { ExprKind::Let(_, _, _, ref mut recovered @ Recovered::No) => { if let Some(reason) = self.forbid_let_reason { let error = match reason { - diagnostics::ForbiddenLetReason::NotSupportedOr(or_span) => { - self.parser.dcx().emit_err(diagnostics::OrInLetChain { span: or_span }) - } + crate::diagnostics::ForbiddenLetReason::NotSupportedOr(or_span) => self + .parser + .dcx() + .emit_err(crate::diagnostics::OrInLetChain { span: or_span }), _ => { let guar = self.parser.dcx().emit_err( - diagnostics::ExpectedExpressionFoundLet { + crate::diagnostics::ExpectedExpressionFoundLet { span, reason, missing_let: self.missing_let, @@ -4336,7 +4299,9 @@ impl MutVisitor for CondChecker<'_> { LetChainsPolicy::AlwaysAllowed => (), LetChainsPolicy::EditionDependent { current_edition } => { if !current_edition.at_least_rust_2024() || !span.at_least_rust_2024() { - self.parser.dcx().emit_err(diagnostics::LetChainPre2024 { span }); + self.parser + .dcx() + .emit_err(crate::diagnostics::LetChainPre2024 { span }); } } } @@ -4346,22 +4311,24 @@ impl MutVisitor for CondChecker<'_> { mut_visit::walk_expr(self, e); } ExprKind::Binary(Spanned { node: BinOpKind::Or, span: or_span }, _, _) - if let None | Some(diagnostics::ForbiddenLetReason::NotSupportedOr(_)) = + if let None | Some(crate::diagnostics::ForbiddenLetReason::NotSupportedOr(_)) = self.forbid_let_reason => { let forbid_let_reason = self.forbid_let_reason; self.forbid_let_reason = - Some(diagnostics::ForbiddenLetReason::NotSupportedOr(or_span)); + Some(crate::diagnostics::ForbiddenLetReason::NotSupportedOr(or_span)); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; } ExprKind::Paren(ref inner) - if let None | Some(diagnostics::ForbiddenLetReason::NotSupportedParentheses(_)) = + if let None + | Some(crate::diagnostics::ForbiddenLetReason::NotSupportedParentheses(_)) = self.forbid_let_reason => { let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = - Some(diagnostics::ForbiddenLetReason::NotSupportedParentheses(inner.span)); + self.forbid_let_reason = Some( + crate::diagnostics::ForbiddenLetReason::NotSupportedParentheses(inner.span), + ); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; } @@ -4399,13 +4366,14 @@ impl MutVisitor for CondChecker<'_> { if let Some(later_rhs) = find_let_some(rhs) && depth > 0 { - let guar = - self.parser.dcx().emit_err(diagnostics::LetChainMissingLet { + let guar = self.parser.dcx().emit_err( + crate::diagnostics::LetChainMissingLet { span: lhs.span, label_span: expr_span, rhs_span: later_rhs.span, sug_span: lhs.span.shrink_to_lo(), - }); + }, + ); self.found_incorrect_let_chain = Some(guar); } @@ -4413,7 +4381,8 @@ impl MutVisitor for CondChecker<'_> { } let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); + self.forbid_let_reason = + Some(crate::diagnostics::ForbiddenLetReason::OtherForbidden); let missing_let = self.missing_let; if let ExprKind::Binary(_, _, rhs) = &lhs.kind && let ExprKind::Path(_, _) @@ -4422,10 +4391,11 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Array(_) = rhs.kind { self.missing_let = - Some(diagnostics::MaybeMissingLet { span: rhs.span.shrink_to_lo() }); + Some(crate::diagnostics::MaybeMissingLet { span: rhs.span.shrink_to_lo() }); } let comparison = self.comparison; - self.comparison = Some(diagnostics::MaybeComparison { span: span.shrink_to_hi() }); + self.comparison = + Some(crate::diagnostics::MaybeComparison { span: span.shrink_to_hi() }); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; self.missing_let = missing_let; @@ -4447,7 +4417,8 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Tup(_) | ExprKind::Paren(_) => { let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); + self.forbid_let_reason = + Some(crate::diagnostics::ForbiddenLetReason::OtherForbidden); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; } @@ -4455,7 +4426,8 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Type(ref mut op, _) | ExprKind::UnsafeBinderCast(_, ref mut op, _) => { let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); + self.forbid_let_reason = + Some(crate::diagnostics::ForbiddenLetReason::OtherForbidden); self.visit_expr(op); self.forbid_let_reason = forbid_let_reason; } diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs new file mode 100644 index 0000000000000..edbf844d5bbb0 --- /dev/null +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -0,0 +1,80 @@ +use rustc_ast::util::parser::AssocOp; +use rustc_ast::{BinOpKind, token}; +use rustc_span::Spanned; + +use crate::diagnostics; +use crate::parser::Parser; + +impl<'a> Parser<'a> { + /// Reject `...` being used as an expression operator. + pub(super) fn reject_dotdotdot_expr_op(&self) { + if self.token == token::DotDotDot { + self.dcx().emit_err(diagnostics::DotDotDotExprOp { span: self.token.span }); + } + } + + /// Reject `<-` being used as an expression operator. + pub(super) fn reject_larrow_expr_op(&self) { + if self.token == token::LArrow { + self.dcx().emit_err(diagnostics::LArrowExprOp { span: self.token.span }); + } + } + + /// Recover from strict equality operators `===` and `!==` as found in e.g., JS and PHP. + pub(super) fn recover_from_strict_eq_op(&mut self, op: Spanned) { + if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node + && self.token == token::Eq + && self.prev_token.span.hi() == self.token.span.lo() + { + let sp = op.span.to(self.token.span); + let sugg = bop.as_str().into(); + let invalid = format!("{sugg}="); + self.dcx().emit_err(diagnostics::InvalidComparisonOperator { + span: sp, + invalid: invalid.clone(), + sub: diagnostics::InvalidComparisonOperatorSub::Correctable { + span: sp, + invalid, + correct: sugg, + }, + }); + self.bump(); + } + } + + /// Recover from inequality operator `<>` ("diamond") as found in e.g., PHP. + pub(super) fn recover_from_diamond_ne_op(&mut self, op: Spanned) { + if op.node == AssocOp::Binary(BinOpKind::Lt) + && self.token == token::Gt + && self.prev_token.span.hi() == self.token.span.lo() + { + let sp = op.span.to(self.token.span); + self.dcx().emit_err(diagnostics::InvalidComparisonOperator { + span: sp, + invalid: "<>".into(), + sub: diagnostics::InvalidComparisonOperatorSub::Correctable { + span: sp, + invalid: "<>".into(), + correct: "!=".into(), + }, + }); + self.bump(); + } + } + + /// Recover from comparison operator `<=>` ("spaceship") as found in e.g., C++. + pub(super) fn recover_from_spaceship_cmp_op(&mut self, op: Spanned) { + if op.node == AssocOp::Binary(BinOpKind::Le) + && self.token == token::Gt + && self.prev_token.span.hi() == self.token.span.lo() + { + let sp = op.span.to(self.token.span); + self.dcx().emit_err(diagnostics::InvalidComparisonOperator { + span: sp, + invalid: "<=>".into(), + sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp), + }); + self.bump(); + } + } +} From b945d684dc870d6cf0bbf058358c9f30e163f830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 10 Sep 2026 13:25:49 +0200 Subject: [PATCH 59/76] Don't needlessly pass the operand through some recovery functions by value These functions didn't actually modifiy the operand or return a new or different expression. So essentially the "`fn(Box) -> Box` part" was an identity function. Just change it to "fn(&Expr)". --- .../rustc_parse/src/parser/diagnostics.rs | 26 ++++++++----------- compiler/rustc_parse/src/parser/expr.rs | 7 ++--- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 40dbda2466de4..64f24b8216edd 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1651,10 +1651,10 @@ impl<'a> Parser<'a> { pub(super) fn recover_from_prefix_increment( &mut self, - operand_expr: Box, + operand_expr: &Expr, op_span: Span, start_stmt: bool, - ) -> PResult<'a, Box> { + ) -> PResult<'a, ()> { let standalone = if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }; let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre }; self.recover_from_inc_dec(operand_expr, kind, op_span) @@ -1662,10 +1662,10 @@ impl<'a> Parser<'a> { pub(super) fn recover_from_postfix_increment( &mut self, - operand_expr: Box, + operand_expr: &Expr, op_span: Span, start_stmt: bool, - ) -> PResult<'a, Box> { + ) -> PResult<'a, ()> { let kind = IncDecRecovery { standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, op: IncOrDec::Inc, @@ -1676,10 +1676,10 @@ impl<'a> Parser<'a> { pub(super) fn recover_from_postfix_decrement( &mut self, - operand_expr: Box, + operand_expr: &Expr, op_span: Span, start_stmt: bool, - ) -> PResult<'a, Box> { + ) -> PResult<'a, ()> { let kind = IncDecRecovery { standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, op: IncOrDec::Dec, @@ -1690,22 +1690,16 @@ impl<'a> Parser<'a> { fn recover_from_inc_dec( &mut self, - base: Box, + base: &Expr, kind: IncDecRecovery, op_span: Span, - ) -> PResult<'a, Box> { + ) -> PResult<'a, ()> { let mut err = self.dcx().struct_span_err( op_span, format!("Rust has no {} {} operator", kind.fixity, kind.op.name()), ); err.span_label(op_span, format!("not a valid {} operator", kind.fixity)); - let help_base_case = |mut err: Diag<'_, ErrorGuaranteed>, base| { - err.help(format!("use `{}= 1` instead", kind.op.chr())); - err.emit(); - Ok(base) - }; - // (pre, post) let spans = match kind.fixity { UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), @@ -1718,7 +1712,9 @@ impl<'a> Parser<'a> { } IsStandalone::Subexpr => { let Ok(base_src) = self.span_to_snippet(base.span) else { - return help_base_case(err, base); + err.help(format!("use `{}= 1` instead", kind.op.chr())); + err.emit(); + return Ok(()); }; match kind.fixity { UnaryFixity::Pre => { diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index a7bcb93d5b084..952c9a3fdf240 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -190,7 +190,7 @@ impl<'a> Parser<'a> { let op_span = self.prev_token.span.to(self.token.span); // Eat the second `+` self.bump(); - lhs = self.recover_from_postfix_increment(lhs, op_span, starts_stmt)?; + self.recover_from_postfix_increment(&lhs, op_span, starts_stmt)?; continue; } @@ -202,7 +202,7 @@ impl<'a> Parser<'a> { let op_span = self.prev_token.span.to(self.token.span); // Eat the second `-` self.bump(); - lhs = self.recover_from_postfix_decrement(lhs, op_span, starts_stmt)?; + self.recover_from_postfix_decrement(&lhs, op_span, starts_stmt)?; continue; } @@ -491,7 +491,8 @@ impl<'a> Parser<'a> { this.bump(); let operand_expr = this.parse_expr_dot_or_call(attrs)?; - this.recover_from_prefix_increment(operand_expr, pre_span, starts_stmt) + this.recover_from_prefix_increment(&operand_expr, pre_span, starts_stmt)?; + Ok(operand_expr) } token::Ident(..) if this.token.is_keyword(kw::Move) From ceede0ba9c7dc2b2fb2fa68dc7b2b8b08e4372cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 6 Sep 2026 22:13:38 +0200 Subject: [PATCH 60/76] Remove odd special case of some parse error recovery functions `recover_from_inc_dec` *always* returns a (fatal) `Err(_)` *except* if the increment/decrement operator is a subexpression *and* the source of the operand is not available in which case it emits the diagnostic and returns `Ok(_)` (rendering it non-fatal). This makes no sense whatsoever. For illustration purposes, listed below are steps that would make us reach this case: 1. `rustc a.rs --crate-type=lib` where `a.rs` contains: `#[macro_export] macro_rules! m { () => { i++ } }`. 2. Move or remove `a.rs` 3. `rustc b.rs --edition 2018 --extern a -L.` where `b.rs` contains: `fn main() { (a::m!()); }`. Just make the error unconditionally fatal and add a FIXME to make it non fatal in the future which would allow us to report name resolution errors and what not. However, since that would be slightly more involved and represent a behavior change (in the error path), this is out of scope for a mere cleanup commit like this one. --- .../rustc_parse/src/parser/diagnostics.rs | 19 ++++++++++++------- compiler/rustc_parse/src/parser/expr.rs | 13 +++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 64f24b8216edd..4aef934323bb3 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1654,7 +1654,7 @@ impl<'a> Parser<'a> { operand_expr: &Expr, op_span: Span, start_stmt: bool, - ) -> PResult<'a, ()> { + ) -> Diag<'a> { let standalone = if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }; let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre }; self.recover_from_inc_dec(operand_expr, kind, op_span) @@ -1665,7 +1665,7 @@ impl<'a> Parser<'a> { operand_expr: &Expr, op_span: Span, start_stmt: bool, - ) -> PResult<'a, ()> { + ) -> Diag<'a> { let kind = IncDecRecovery { standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, op: IncOrDec::Inc, @@ -1679,7 +1679,7 @@ impl<'a> Parser<'a> { operand_expr: &Expr, op_span: Span, start_stmt: bool, - ) -> PResult<'a, ()> { + ) -> Diag<'a> { let kind = IncDecRecovery { standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, op: IncOrDec::Dec, @@ -1693,7 +1693,13 @@ impl<'a> Parser<'a> { base: &Expr, kind: IncDecRecovery, op_span: Span, - ) -> PResult<'a, ()> { + ) -> Diag<'a> { + // FIXME: Don't return an error diag, emit the diag here *and* return a new expr of the form + // `$base += 1` / `$base -= 1` (taking `base: Expr` by value) for *proper* recovery. + // (Just emitting the diag would be insufficient since callers would most likely just + // use `$base` as the recovered AST node which would lead to annoying follow-up diags + // like "variable doesn't need to be mutable" getting emitted in some cases.) + let mut err = self.dcx().struct_span_err( op_span, format!("Rust has no {} {} operator", kind.fixity, kind.op.name()), @@ -1713,8 +1719,7 @@ impl<'a> Parser<'a> { IsStandalone::Subexpr => { let Ok(base_src) = self.span_to_snippet(base.span) else { err.help(format!("use `{}= 1` instead", kind.op.chr())); - err.emit(); - return Ok(()); + return err; }; match kind.fixity { UnaryFixity::Pre => { @@ -1730,7 +1735,7 @@ impl<'a> Parser<'a> { } } } - Err(err) + err } fn prefix_inc_dec_suggest( diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 952c9a3fdf240..c179db3dbfa09 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -190,8 +190,7 @@ impl<'a> Parser<'a> { let op_span = self.prev_token.span.to(self.token.span); // Eat the second `+` self.bump(); - self.recover_from_postfix_increment(&lhs, op_span, starts_stmt)?; - continue; + return Err(self.recover_from_postfix_increment(&lhs, op_span, starts_stmt)); } if self.prev_token == token::Minus @@ -202,8 +201,7 @@ impl<'a> Parser<'a> { let op_span = self.prev_token.span.to(self.token.span); // Eat the second `-` self.bump(); - self.recover_from_postfix_decrement(&lhs, op_span, starts_stmt)?; - continue; + return Err(self.recover_from_postfix_decrement(&lhs, op_span, starts_stmt)); } let op_span = op.span; @@ -490,9 +488,8 @@ impl<'a> Parser<'a> { this.bump(); this.bump(); - let operand_expr = this.parse_expr_dot_or_call(attrs)?; - this.recover_from_prefix_increment(&operand_expr, pre_span, starts_stmt)?; - Ok(operand_expr) + let operand = this.parse_expr_dot_or_call(attrs)?; + return Err(this.recover_from_prefix_increment(&operand, pre_span, starts_stmt)); } token::Ident(..) if this.token.is_keyword(kw::Move) @@ -503,7 +500,7 @@ impl<'a> Parser<'a> { token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => { make_it!(this, attrs, |this, _| this.recover_not_expr(lo)) } - _ => return this.parse_expr_dot_or_call(attrs), + _ => this.parse_expr_dot_or_call(attrs), } } From 6c0ab88a2037fa75cc8d78d8b06a272f483e4710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 6 Sep 2026 22:45:43 +0200 Subject: [PATCH 61/76] Dismantle bespoke diagnostic suggestion wrapper API There's literally no upside to use it and only downsides: It's not more concise, only adds code and obfuscates. Its `MultiSugg::emit{,_verbose}` didn't even *emit* the diagnostic, they merely *decorated* it! --- .../rustc_parse/src/parser/diagnostics.rs | 109 ++++++------------ 1 file changed, 36 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 4aef934323bb3..3263fcacec498 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -211,22 +211,6 @@ fn find_similar_kw(lookup: Ident, candidates: &[Symbol]) -> Option }) } -struct MultiSugg { - msg: String, - patches: Vec<(Span, String)>, - applicability: Applicability, -} - -impl MultiSugg { - fn emit(self, err: &mut Diag<'_>) { - err.multipart_suggestion(self.msg, self.patches, self.applicability); - } - - fn emit_verbose(self, err: &mut Diag<'_>) { - err.multipart_suggestion(self.msg, self.patches, self.applicability); - } -} - /// SnapshotParser is used to create a snapshot of the parser /// without causing duplicate errors being emitted when the `Parser` /// is dropped. @@ -1706,15 +1690,23 @@ impl<'a> Parser<'a> { ); err.span_label(op_span, format!("not a valid {} operator", kind.fixity)); - // (pre, post) - let spans = match kind.fixity { + let (pre_span, post_span) = match kind.fixity { UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), }; match kind.standalone { IsStandalone::Standalone => { - self.inc_dec_standalone_suggest(kind, spans).emit_verbose(&mut err) + let mut patches = Vec::new(); + if !pre_span.is_empty() { + patches.push((pre_span, String::new())); + } + patches.push((post_span, format!(" {}= 1", kind.op.chr()))); + err.multipart_suggestion( + format!("use `{}= 1` instead", kind.op.chr()), + patches, + Applicability::MachineApplicable, + ); } IsStandalone::Subexpr => { let Ok(base_src) = self.span_to_snippet(base.span) else { @@ -1723,13 +1715,36 @@ impl<'a> Parser<'a> { }; match kind.fixity { UnaryFixity::Pre => { - self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) + err.multipart_suggestion( + format!("use `{}= 1` instead", kind.op.chr()), + vec![ + (pre_span, "{ ".to_string()), + (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)), + ], + Applicability::MachineApplicable, + ); } UnaryFixity::Post => { // won't suggest since we can not handle the precedences // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here if !matches!(base.kind, ExprKind::Binary(_, _, _)) { - self.postfix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) + let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; + err.multipart_suggestion( + format!("use `{}= 1` instead", kind.op.chr()), + vec![ + (pre_span, format!("{{ let {tmp_var} = ")), + ( + post_span, + format!( + "; {} {}= 1; {} }}", + base_src, + kind.op.chr(), + tmp_var + ), + ), + ], + Applicability::HasPlaceholders, + ); } } } @@ -1738,58 +1753,6 @@ impl<'a> Parser<'a> { err } - fn prefix_inc_dec_suggest( - &mut self, - base_src: String, - kind: IncDecRecovery, - (pre_span, post_span): (Span, Span), - ) -> MultiSugg { - MultiSugg { - msg: format!("use `{}= 1` instead", kind.op.chr()), - patches: vec![ - (pre_span, "{ ".to_string()), - (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)), - ], - applicability: Applicability::MachineApplicable, - } - } - - fn postfix_inc_dec_suggest( - &mut self, - base_src: String, - kind: IncDecRecovery, - (pre_span, post_span): (Span, Span), - ) -> MultiSugg { - let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; - MultiSugg { - msg: format!("use `{}= 1` instead", kind.op.chr()), - patches: vec![ - (pre_span, format!("{{ let {tmp_var} = ")), - (post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)), - ], - applicability: Applicability::HasPlaceholders, - } - } - - fn inc_dec_standalone_suggest( - &mut self, - kind: IncDecRecovery, - (pre_span, post_span): (Span, Span), - ) -> MultiSugg { - let mut patches = Vec::new(); - - if !pre_span.is_empty() { - patches.push((pre_span, String::new())); - } - - patches.push((post_span, format!(" {}= 1", kind.op.chr()))); - MultiSugg { - msg: format!("use `{}= 1` instead", kind.op.chr()), - patches, - applicability: Applicability::MachineApplicable, - } - } - /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`. /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem` /// tail, and combines them into a `::AssocItem` expression/pattern/type. From ddb9380cde72a3abd88c8521e00c380451aa0e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 10 Sep 2026 10:37:14 +0200 Subject: [PATCH 62/76] Move parse error recovery from C-style inc/dec ops out of line --- compiler/rustc_parse/src/parser/expr.rs | 23 +---------- .../src/parser/expr/diagnostics.rs | 38 ++++++++++++++++++- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index c179db3dbfa09..1349af251e649 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -182,27 +182,8 @@ impl<'a> Parser<'a> { self.recover_from_strict_eq_op(op); self.recover_from_diamond_ne_op(op); self.recover_from_spaceship_cmp_op(op); - - if self.prev_token == token::Plus - && self.token == token::Plus - && self.prev_token.span.between(self.token.span).is_empty() - { - let op_span = self.prev_token.span.to(self.token.span); - // Eat the second `+` - self.bump(); - return Err(self.recover_from_postfix_increment(&lhs, op_span, starts_stmt)); - } - - if self.prev_token == token::Minus - && self.token == token::Minus - && self.prev_token.span.between(self.token.span).is_empty() - && !self.look_ahead(1, |tok| tok.can_begin_expr()) - { - let op_span = self.prev_token.span.to(self.token.span); - // Eat the second `-` - self.bump(); - return Err(self.recover_from_postfix_decrement(&lhs, op_span, starts_stmt)); - } + self.recover_from_postfix_inc_op(&lhs, starts_stmt)?; + self.recover_from_postfix_dec_op(&lhs, starts_stmt)?; let op_span = op.span; let op = op.node; diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs index edbf844d5bbb0..0008b576fdb4b 100644 --- a/compiler/rustc_parse/src/parser/expr/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -1,5 +1,6 @@ use rustc_ast::util::parser::AssocOp; -use rustc_ast::{BinOpKind, token}; +use rustc_ast::{BinOpKind, Expr, token}; +use rustc_errors::PResult; use rustc_span::Spanned; use crate::diagnostics; @@ -77,4 +78,39 @@ impl<'a> Parser<'a> { self.bump(); } } + + /// Recover from postfix increment operator `++` as found in many C-style languages. + pub(super) fn recover_from_postfix_inc_op( + &mut self, + lhs: &Expr, + starts_stmt: bool, + ) -> PResult<'a, ()> { + if let (token::Plus, token::Plus) = (self.prev_token.kind, self.token.kind) + && self.prev_token.span.hi() == self.token.span.lo() + { + let op_span = self.prev_token.span.to(self.token.span); + self.bump(); // eat the second `+` + Err(self.recover_from_postfix_increment(lhs, op_span, starts_stmt)) + } else { + Ok(()) + } + } + + /// Recover from postfix decrement operator `--` as found in many C-style languages. + pub(super) fn recover_from_postfix_dec_op( + &mut self, + lhs: &Expr, + starts_stmt: bool, + ) -> PResult<'a, ()> { + if let (token::Minus, token::Minus) = (self.prev_token.kind, self.token.kind) + && self.prev_token.span.hi() == self.token.span.lo() + && !self.look_ahead(1, |tok| tok.can_begin_expr()) + { + let op_span = self.prev_token.span.to(self.token.span); + self.bump(); // eat the second `-` + Err(self.recover_from_postfix_decrement(lhs, op_span, starts_stmt)) + } else { + Ok(()) + } + } } From 1adccf221ff9c6c507354395f255aa973c5a3693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 10 Sep 2026 10:59:21 +0200 Subject: [PATCH 63/76] Inline fns & data types related to parse error recovery from C-style inc/dec ops --- .../rustc_parse/src/parser/diagnostics.rs | 178 ------------------ compiler/rustc_parse/src/parser/expr.rs | 8 +- .../src/parser/expr/diagnostics.rs | 104 +++++++++- 3 files changed, 106 insertions(+), 184 deletions(-) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 3263fcacec498..f5fa592585099 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -141,64 +141,6 @@ impl AttemptLocalParseRecovery { } } -/// Information for emitting suggestions and recovering from -/// C-style `i++`, `--i`, etc. -#[derive(Debug, Copy, Clone)] -struct IncDecRecovery { - /// Is this increment/decrement its own statement? - standalone: IsStandalone, - /// Is this an increment or decrement? - op: IncOrDec, - /// Is this pre- or postfix? - fixity: UnaryFixity, -} - -/// Is an increment or decrement expression its own statement? -#[derive(Debug, Copy, Clone)] -enum IsStandalone { - /// It's standalone, i.e., its own statement. - Standalone, - /// It's a subexpression, i.e., *not* standalone. - Subexpr, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum IncOrDec { - Inc, - Dec, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum UnaryFixity { - Pre, - Post, -} - -impl IncOrDec { - fn chr(&self) -> char { - match self { - Self::Inc => '+', - Self::Dec => '-', - } - } - - fn name(&self) -> &'static str { - match self { - Self::Inc => "increment", - Self::Dec => "decrement", - } - } -} - -impl std::fmt::Display for UnaryFixity { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Pre => write!(f, "prefix"), - Self::Post => write!(f, "postfix"), - } - } -} - /// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`. /// /// This is a specialized version of [`Symbol::find_similar`] that constructs an error when a @@ -1633,126 +1575,6 @@ impl<'a> Parser<'a> { Ok(()) } - pub(super) fn recover_from_prefix_increment( - &mut self, - operand_expr: &Expr, - op_span: Span, - start_stmt: bool, - ) -> Diag<'a> { - let standalone = if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }; - let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre }; - self.recover_from_inc_dec(operand_expr, kind, op_span) - } - - pub(super) fn recover_from_postfix_increment( - &mut self, - operand_expr: &Expr, - op_span: Span, - start_stmt: bool, - ) -> Diag<'a> { - let kind = IncDecRecovery { - standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, - op: IncOrDec::Inc, - fixity: UnaryFixity::Post, - }; - self.recover_from_inc_dec(operand_expr, kind, op_span) - } - - pub(super) fn recover_from_postfix_decrement( - &mut self, - operand_expr: &Expr, - op_span: Span, - start_stmt: bool, - ) -> Diag<'a> { - let kind = IncDecRecovery { - standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, - op: IncOrDec::Dec, - fixity: UnaryFixity::Post, - }; - self.recover_from_inc_dec(operand_expr, kind, op_span) - } - - fn recover_from_inc_dec( - &mut self, - base: &Expr, - kind: IncDecRecovery, - op_span: Span, - ) -> Diag<'a> { - // FIXME: Don't return an error diag, emit the diag here *and* return a new expr of the form - // `$base += 1` / `$base -= 1` (taking `base: Expr` by value) for *proper* recovery. - // (Just emitting the diag would be insufficient since callers would most likely just - // use `$base` as the recovered AST node which would lead to annoying follow-up diags - // like "variable doesn't need to be mutable" getting emitted in some cases.) - - let mut err = self.dcx().struct_span_err( - op_span, - format!("Rust has no {} {} operator", kind.fixity, kind.op.name()), - ); - err.span_label(op_span, format!("not a valid {} operator", kind.fixity)); - - let (pre_span, post_span) = match kind.fixity { - UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), - UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), - }; - - match kind.standalone { - IsStandalone::Standalone => { - let mut patches = Vec::new(); - if !pre_span.is_empty() { - patches.push((pre_span, String::new())); - } - patches.push((post_span, format!(" {}= 1", kind.op.chr()))); - err.multipart_suggestion( - format!("use `{}= 1` instead", kind.op.chr()), - patches, - Applicability::MachineApplicable, - ); - } - IsStandalone::Subexpr => { - let Ok(base_src) = self.span_to_snippet(base.span) else { - err.help(format!("use `{}= 1` instead", kind.op.chr())); - return err; - }; - match kind.fixity { - UnaryFixity::Pre => { - err.multipart_suggestion( - format!("use `{}= 1` instead", kind.op.chr()), - vec![ - (pre_span, "{ ".to_string()), - (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)), - ], - Applicability::MachineApplicable, - ); - } - UnaryFixity::Post => { - // won't suggest since we can not handle the precedences - // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here - if !matches!(base.kind, ExprKind::Binary(_, _, _)) { - let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; - err.multipart_suggestion( - format!("use `{}= 1` instead", kind.op.chr()), - vec![ - (pre_span, format!("{{ let {tmp_var} = ")), - ( - post_span, - format!( - "; {} {}= 1; {} }}", - base_src, - kind.op.chr(), - tmp_var - ), - ), - ], - Applicability::HasPlaceholders, - ); - } - } - } - } - } - err - } - /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`. /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem` /// tail, and combines them into a `::AssocItem` expression/pattern/type. diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 1349af251e649..54e10d05f1140 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -470,7 +470,13 @@ impl<'a> Parser<'a> { this.bump(); let operand = this.parse_expr_dot_or_call(attrs)?; - return Err(this.recover_from_prefix_increment(&operand, pre_span, starts_stmt)); + return Err(this.report_inc_dec_op( + &operand, + starts_stmt, + diagnostics::IncOrDec::Inc, + diagnostics::UnaryFixity::Pre, + pre_span, + )); } token::Ident(..) if this.token.is_keyword(kw::Move) diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs index 0008b576fdb4b..18dd6dba13eb5 100644 --- a/compiler/rustc_parse/src/parser/expr/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -1,7 +1,7 @@ use rustc_ast::util::parser::AssocOp; -use rustc_ast::{BinOpKind, Expr, token}; -use rustc_errors::PResult; -use rustc_span::Spanned; +use rustc_ast::{BinOpKind, Expr, ExprKind, token}; +use rustc_errors::{Applicability, Diag, PResult}; +use rustc_span::{Span, Spanned}; use crate::diagnostics; use crate::parser::Parser; @@ -90,7 +90,7 @@ impl<'a> Parser<'a> { { let op_span = self.prev_token.span.to(self.token.span); self.bump(); // eat the second `+` - Err(self.recover_from_postfix_increment(lhs, op_span, starts_stmt)) + Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Inc, UnaryFixity::Post, op_span)) } else { Ok(()) } @@ -108,9 +108,103 @@ impl<'a> Parser<'a> { { let op_span = self.prev_token.span.to(self.token.span); self.bump(); // eat the second `-` - Err(self.recover_from_postfix_decrement(lhs, op_span, starts_stmt)) + Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Dec, UnaryFixity::Post, op_span)) } else { Ok(()) } } + + /// Report increment operator `++` & decrement operator `--` as found in many C-style languages. + pub(super) fn report_inc_dec_op( + &mut self, + base: &Expr, + starts_stmt: bool, + op: IncOrDec, + fixity: UnaryFixity, + op_span: Span, + ) -> Diag<'a> { + // FIXME: Don't return an error diag, emit the diag here *and* return a new expr of the form + // `$base += 1` / `$base -= 1` (taking `base: Expr` by value) for *proper* recovery. + // (Just emitting the diag would be insufficient since callers would most likely just + // use `$base` as the recovered AST node which would lead to annoying follow-up diags + // like "variable doesn't need to be mutable" getting emitted in some cases.) + + let mut err = { + let fixity = match fixity { + UnaryFixity::Pre => "prefix", + UnaryFixity::Post => "postfix", + }; + let op = match op { + IncOrDec::Inc => "increment", + IncOrDec::Dec => "decrement", + }; + self.dcx() + .struct_span_err(op_span, format!("Rust has no {fixity} {op} operator")) + .with_span_label(op_span, format!("not a valid {fixity} operator")) + }; + + let op = match op { + IncOrDec::Inc => "+= 1", + IncOrDec::Dec => "-= 1", + }; + let (pre_span, post_span) = match fixity { + UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), + UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), + }; + + if starts_stmt { + let mut patches = Vec::new(); + if !pre_span.is_empty() { + patches.push((pre_span, String::new())); + } + patches.push((post_span, format!(" {op}"))); + err.multipart_suggestion( + format!("use `{op}` instead"), + patches, + Applicability::MachineApplicable, + ); + } else { + let Ok(base_src) = self.span_to_snippet(base.span) else { + err.help(format!("use `{op}` instead")); + return err; + }; + match fixity { + UnaryFixity::Pre => { + err.multipart_suggestion( + format!("use `{op}` instead"), + vec![(pre_span, "{ ".into()), (post_span, format!(" {op}; {base_src} }}"))], + Applicability::MachineApplicable, + ); + } + UnaryFixity::Post => { + // won't suggest since we can not handle the precedences + // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here + if !matches!(base.kind, ExprKind::Binary(..)) { + let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; + err.multipart_suggestion( + format!("use `{op}` instead"), + vec![ + (pre_span, format!("{{ let {tmp_var} = ")), + (post_span, format!("; {base_src} {op}; {tmp_var} }}")), + ], + Applicability::HasPlaceholders, + ); + } + } + } + } + err + } +} + +#[derive(Copy, Clone)] +pub(super) enum IncOrDec { + Inc, + Dec, +} + +#[derive(Copy, Clone)] +pub(super) enum UnaryFixity { + Pre, + Post, } From d85d3f55051e0d88237c0f4c0b4070f73d5ef837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 10 Sep 2026 12:05:49 +0200 Subject: [PATCH 64/76] Refactor the way we finish parsing expr ops 1. Remove unnecessary rebindings (`op_span` and `op = op.node`) 2. Remove binding `cur_op_span` as it's equal to `op.span` 3. Merge two `match`es on `op.node` into one to make the control flow more obvious and to render everything more legible. Moreover, it allows us to drop an ungly `unreachable!()` --- compiler/rustc_parse/src/parser/expr.rs | 56 ++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 54e10d05f1140..336af55a4e904 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -152,7 +152,6 @@ impl<'a> Parser<'a> { self.expected_token_types.insert(TokenType::Operator); while let Some(op) = self.check_assoc_op() { let lhs_span = self.interpolated_or_expr_span(&lhs); - let cur_op_span = self.token.span; let restrictions = if op.node.is_assign_like() { self.restrictions & Restrictions::NO_STRUCT_LITERAL } else { @@ -185,42 +184,41 @@ impl<'a> Parser<'a> { self.recover_from_postfix_inc_op(&lhs, starts_stmt)?; self.recover_from_postfix_dec_op(&lhs, starts_stmt)?; - let op_span = op.span; - let op = op.node; - // Special cases: - if op == AssocOp::Cast { - lhs = self.parse_assoc_op_cast(lhs, lhs_span, op_span, ExprKind::Cast)?; - continue; - } else if let AssocOp::Range(limits) = op { - // If we didn't have to handle `x..`/`x..=`, it would be pretty easy to - // generalise it to the Fixity::None code. - lhs = self.parse_expr_range(prec, lhs, limits, cur_op_span)?; - break; - } - - let min_prec = match op.fixity() { + let min_prec = match op.node.fixity() { Fixity::Right => Bound::Included(prec), Fixity::Left | Fixity::None => Bound::Excluded(prec), }; - let rhs = self.with_res(restrictions - Restrictions::STMT_EXPR, |this| { - this.parse_expr_assoc(min_prec) - })?; - let span = self.mk_expr_sp(&lhs, lhs_span, op_span, rhs.span); - lhs = match op { + let finish_parsing_bin_op = |this: &mut Self| { + let rhs = this.with_res(restrictions - Restrictions::STMT_EXPR, |this| { + this.parse_expr_assoc(min_prec) + })?; + let span = this.mk_expr_sp(&lhs, lhs_span, op.span, rhs.span); + Ok((rhs, span)) + }; + + lhs = match op.node { AssocOp::Binary(ast_op) => { - let binary = self.mk_binary(respan(cur_op_span, ast_op), lhs, rhs); - self.mk_expr(span, binary) + let (rhs, span) = finish_parsing_bin_op(self)?; + self.mk_expr(span, self.mk_binary(respan(op.span, ast_op), lhs, rhs)) } - AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs, cur_op_span)), AssocOp::AssignOp(aop) => { - let aopexpr = self.mk_assign_op(respan(cur_op_span, aop), lhs, rhs); - self.mk_expr(span, aopexpr) + let (rhs, span) = finish_parsing_bin_op(self)?; + self.mk_expr(span, self.mk_assign_op(respan(op.span, aop), lhs, rhs)) + } + AssocOp::Assign => { + let (rhs, span) = finish_parsing_bin_op(self)?; + self.mk_expr(span, ExprKind::Assign(lhs, rhs, op.span)) } - AssocOp::Cast | AssocOp::Range(_) => { - self.dcx().span_bug(span, "AssocOp should have been handled by special case") + AssocOp::Cast => { + self.parse_assoc_op_cast(lhs, lhs_span, op.span, ExprKind::Cast)? } + AssocOp::Range(limits) => self.parse_expr_range(min_prec, lhs, limits, op.span)?, }; + + if let AssocOp::Range(_) = op.node { + break; + } } Ok((lhs, parsed_something)) @@ -338,7 +336,7 @@ impl<'a> Parser<'a> { /// The other two variants are handled in `parse_prefix_range_expr` below. fn parse_expr_range( &mut self, - prec: ExprPrecedence, + min_prec: Bound, lhs: Box, limits: RangeLimits, cur_op_span: Span, @@ -346,7 +344,7 @@ impl<'a> Parser<'a> { let rhs = if self.is_at_start_of_range_notation_rhs() { let maybe_lt = self.token; Some( - self.parse_expr_assoc(Bound::Excluded(prec)) + self.parse_expr_assoc(min_prec) .map_err(|err| self.maybe_err_dotdotlt_syntax(maybe_lt, err))?, ) } else { From f0ae097b364ffd9b30f908aeddc0b9eb040464dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 25 Aug 2026 16:30:11 +0200 Subject: [PATCH 65/76] Refactor `check_assoc_op` to make it more legible --- compiler/rustc_parse/src/diagnostics.rs | 2 +- compiler/rustc_parse/src/parser/expr.rs | 80 +++++++------------ .../src/parser/expr/diagnostics.rs | 25 +++++- 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 78240b0ee891b..4f3c702c77ef9 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -257,7 +257,7 @@ pub(crate) enum InvalidComparisonOperatorSub { pub(crate) struct InvalidLogicalOperator { #[primary_span] pub span: Span, - pub incorrect: String, + pub incorrect: Symbol, #[subdiagnostic] pub sub: InvalidLogicalOperatorSub, } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 336af55a4e904..148e49d803875 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -272,59 +272,35 @@ impl<'a> Parser<'a> { /// Possibly translate the current token to an associative operator. /// The method does not advance the current token. - /// - /// Also performs recovery for `and` / `or` which are mistaken for `&&` and `||` respectively. pub(super) fn check_assoc_op(&self) -> Option> { - let (op, span) = match (AssocOp::from_token(&self.token), self.token.ident()) { - // When parsing const expressions, stop parsing when encountering `>`. - ( - Some( - AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge) - | AssocOp::AssignOp(AssignOpKind::ShrAssign), - ), - _, - ) if self.restrictions.contains(Restrictions::CONST_EXPR) => { - return None; - } - // When recovering patterns as expressions, stop parsing when encountering an - // assignment `=`, an alternative `|`, or a range `..`. - ( - Some( - AssocOp::Assign - | AssocOp::AssignOp(_) - | AssocOp::Binary(BinOpKind::BitOr) - | AssocOp::Range(_), - ), - _, - ) if self.restrictions.contains(Restrictions::IS_PAT) => { - return None; - } - (Some(op), _) => (op, self.token.span), - (None, Some((Ident { name: sym::and, span }, IdentIsRaw::No))) - if self.may_recover() => - { - self.dcx().emit_err(crate::diagnostics::InvalidLogicalOperator { - span: self.token.span, - incorrect: "and".into(), - sub: crate::diagnostics::InvalidLogicalOperatorSub::Conjunction( - self.token.span, - ), - }); - (AssocOp::Binary(BinOpKind::And), span) - } - (None, Some((Ident { name: sym::or, span }, IdentIsRaw::No))) if self.may_recover() => { - self.dcx().emit_err(crate::diagnostics::InvalidLogicalOperator { - span: self.token.span, - incorrect: "or".into(), - sub: crate::diagnostics::InvalidLogicalOperatorSub::Disjunction( - self.token.span, - ), - }); - (AssocOp::Binary(BinOpKind::Or), span) - } - _ => return None, - }; - Some(respan(span, op)) + let op = AssocOp::from_token(&self.token); + + // When parsing const expressions, stop parsing when encountering `>`. + if self.restrictions.contains(Restrictions::CONST_EXPR) + && let Some(op) = op + && let AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge) + | AssocOp::AssignOp(AssignOpKind::ShrAssign) = op + { + return None; + } + + // When recovering patterns as expressions, stop parsing when encountering an + // assignment `=`, an alternative `|`, or a range `..`. + if self.restrictions.contains(Restrictions::IS_PAT) + && let Some(op) = op + && let AssocOp::Assign + | AssocOp::AssignOp(_) + | AssocOp::Binary(BinOpKind::BitOr) + | AssocOp::Range(_) = op + { + return None; + } + + if let Some(op) = op { + return Some(respan(self.token.span, op)); + } + + self.recover_from_alpha_logic_op() } /// Checks if this expression is a successfully parsed statement. diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs index 18dd6dba13eb5..4b9e288f3ec1b 100644 --- a/compiler/rustc_parse/src/parser/expr/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -1,12 +1,35 @@ use rustc_ast::util::parser::AssocOp; use rustc_ast::{BinOpKind, Expr, ExprKind, token}; use rustc_errors::{Applicability, Diag, PResult}; -use rustc_span::{Span, Spanned}; +use rustc_span::{Span, Spanned, respan, sym}; use crate::diagnostics; use crate::parser::Parser; impl<'a> Parser<'a> { + /// Recover from alphabetic logic operators `and` and `or` as found in e.g., Python and PHP. + pub(super) fn recover_from_alpha_logic_op(&self) -> Option> { + if self.may_recover() + && let Some((ident, token::IdentIsRaw::No)) = self.token.ident() + { + let (op, sub): (_, fn(_) -> _) = match ident.name { + sym::and => (BinOpKind::And, diagnostics::InvalidLogicalOperatorSub::Conjunction), + sym::or => (BinOpKind::Or, diagnostics::InvalidLogicalOperatorSub::Disjunction), + _ => return None, + }; + + self.dcx().emit_err(diagnostics::InvalidLogicalOperator { + span: self.token.span, + incorrect: ident.name, + sub: sub(self.token.span), + }); + + Some(respan(self.token.span, AssocOp::Binary(op))) + } else { + None + } + } + /// Reject `...` being used as an expression operator. pub(super) fn reject_dotdotdot_expr_op(&self) { if self.token == token::DotDotDot { From 28158721a2f166c635a091b88f09650e3f28da57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 11 Sep 2026 10:24:51 +0200 Subject: [PATCH 66/76] Don't mistake `<->` for `<>` Previously we would check if the current operator was `Binary(Lt)` and the current token was `>` to determine if we're looking at `<>`. However, since `AssocOp::from_token` also treats `<-` as `Binary(Lt)` for better error recovery, the condition would also hold for `<->` (`<-`, `>`) which is not what we want. E.g., given `1 <-> 2` we would previously emit diagnostic "invalid comparison operator `<>`". --- Also update `recover_from_spaceship_cmp_op` to do something similar -- not to fix anything but simply to eliminate param `op: Spanned`. --- compiler/rustc_parse/src/parser/expr.rs | 4 ++-- .../rustc_parse/src/parser/expr/diagnostics.rs | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 148e49d803875..58e98a64b5e41 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -179,8 +179,8 @@ impl<'a> Parser<'a> { } self.recover_from_strict_eq_op(op); - self.recover_from_diamond_ne_op(op); - self.recover_from_spaceship_cmp_op(op); + self.recover_from_diamond_ne_op(); + self.recover_from_spaceship_cmp_op(); self.recover_from_postfix_inc_op(&lhs, starts_stmt)?; self.recover_from_postfix_dec_op(&lhs, starts_stmt)?; diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs index 4b9e288f3ec1b..707ae5d34bc75 100644 --- a/compiler/rustc_parse/src/parser/expr/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -67,12 +67,11 @@ impl<'a> Parser<'a> { } /// Recover from inequality operator `<>` ("diamond") as found in e.g., PHP. - pub(super) fn recover_from_diamond_ne_op(&mut self, op: Spanned) { - if op.node == AssocOp::Binary(BinOpKind::Lt) - && self.token == token::Gt + pub(super) fn recover_from_diamond_ne_op(&mut self) { + if let (token::Lt, token::Gt) = (self.prev_token.kind, self.token.kind) && self.prev_token.span.hi() == self.token.span.lo() { - let sp = op.span.to(self.token.span); + let sp = self.prev_token.span.to(self.token.span); self.dcx().emit_err(diagnostics::InvalidComparisonOperator { span: sp, invalid: "<>".into(), @@ -87,12 +86,11 @@ impl<'a> Parser<'a> { } /// Recover from comparison operator `<=>` ("spaceship") as found in e.g., C++. - pub(super) fn recover_from_spaceship_cmp_op(&mut self, op: Spanned) { - if op.node == AssocOp::Binary(BinOpKind::Le) - && self.token == token::Gt + pub(super) fn recover_from_spaceship_cmp_op(&mut self) { + if let (token::Le, token::Gt) = (self.prev_token.kind, self.token.kind) && self.prev_token.span.hi() == self.token.span.lo() { - let sp = op.span.to(self.token.span); + let sp = self.prev_token.span.to(self.token.span); self.dcx().emit_err(diagnostics::InvalidComparisonOperator { span: sp, invalid: "<=>".into(), From c99166fac95d61cfd29987ba10b52a54567084ee Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 19:43:46 +0200 Subject: [PATCH 67/76] Revert "improve tests/ecosystem-test-jobs/fuchsia.md" This reverts commit 0bb5cd0fb339d3d1765af05fc1411a5742cb9d12. --- .../src/tests/ecosystem-test-jobs/fuchsia.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index edbb3b93a0808..124e8dfcda18d 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -47,7 +47,7 @@ The main reason you would want to build Fuchsia locally is because you need to investigate a regression. After running a Docker build, you'll find the Fuchsia checkout inside the `obj/fuchsia` directory of your Rust checkout. -If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to + If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to `KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build command above. This will reuse all the build results from before. @@ -70,11 +70,11 @@ There are a few `fx` subcommands that are relevant, including: - `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. - `fx build` builds the Fuchsia project using Ninja. It will automatically pick up changes to build arguments and rerun GN. - By default, it builds everything, + By default it builds everything, but it also accepts target paths to build specific targets (see below). - `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this in the Rust CI build to avoid running codegen on most Rust targets. - Underneath, it invokes Ninja, just like `fx build`. + Underneath it invokes Ninja, just like `fx build`. The clippy results are saved in json files inside the build output directory before being printed. @@ -135,7 +135,7 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will rebuild all the Rust targets. -This can be done in a text editor, and the contents of the string do not matter, +This can be done in a text editor and the contents of the string do not matter, as long as it changes from one build to the next. [build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. @@ -145,7 +145,7 @@ The Fuchsia website has more detailed documentation of the [build system]. When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` command after the initial run so it won't rerun GN each time. -If you do this, you can also comment out the version_string line to save a few seconds. +If you do this you can also comment out the version_string line to save a couple seconds. `export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. From 2e95aa2091c588e5d487faa3e3df925ed68a5f79 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 19:44:08 +0200 Subject: [PATCH 68/76] Revert "sembr src/tests/ecosystem-test-jobs/fuchsia.md" This reverts commit fcfdbf39d50bf291f24b55ce4d3d80939f4f7f37. --- .../src/tests/ecosystem-test-jobs/fuchsia.md | 98 ++++++++++--------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index 124e8dfcda18d..75cf782a77025 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -14,7 +14,8 @@ Please contact the [fuchsia][fuchsia-ping] ping group and ask them for help. ## Building Fuchsia in CI -Fuchsia builds as part of the suite of bors tests that run before a pull request is merged. +Fuchsia builds as part of the suite of bors tests that run before a pull request +is merged. If you are worried that a pull request might break the Fuchsia builder and want to test it out before submitting it to the bors queue, simply ask bors to run @@ -22,13 +23,13 @@ the try job that builds the Fuchsia integration: `@bors try jobs=x86_64-fuchsia` ## Building Fuchsia locally -Because Fuchsia uses languages other than Rust, it does not use Cargo as a build system. -It also requires the toolchain build to be configured in a [certain way][build-toolchain]. +Because Fuchsia uses languages other than Rust, it does not use Cargo as a build +system. It also requires the toolchain build to be configured in a [certain +way][build-toolchain]. The recommended way to build Fuchsia is to use the Docker scripts that check out -and run a Fuchsia build for you. -If you've run Docker tests before, -you can simply run this command from your Rust checkout to download and build Fuchsia +and run a Fuchsia build for you. If you've run Docker tests before, you can +simply run this command from your Rust checkout to download and build Fuchsia using your local Rust toolchain. ``` @@ -39,19 +40,20 @@ See the [Testing with Docker](../docker.md) chapter for more details on how to r and debug jobs with Docker. Note that a Fuchsia checkout is *large* – as of this writing, a checkout and -build takes 46G of space – and as you might imagine, it takes a while to complete. +build takes 46G of space – and as you might imagine, it takes a while to +complete. ### Modifying the Fuchsia checkout The main reason you would want to build Fuchsia locally is because you need to -investigate a regression. -After running a Docker build, -you'll find the Fuchsia checkout inside the `obj/fuchsia` directory of your Rust checkout. - If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to -`KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build command above. -This will reuse all the build results from before. +investigate a regression. After running a Docker build, you'll find the Fuchsia +checkout inside the `obj/fuchsia` directory of your Rust checkout. If you +modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to +`KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build +command above. This will reuse all the build results from before. -You can find more options to customize the Fuchsia checkout in the [build-fuchsia.sh] script. +You can find more options to customize the Fuchsia checkout in the +[build-fuchsia.sh] script. ### Customizing the Fuchsia build @@ -67,15 +69,14 @@ to add this to your `$PATH` for some workflows. There are a few `fx` subcommands that are relevant, including: -- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. -- `fx build` builds the Fuchsia project using Ninja. - It will automatically pick up changes to build arguments and rerun GN. - By default it builds everything, +- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and + runs GN. +- `fx build` builds the Fuchsia project using Ninja. It will automatically pick + up changes to build arguments and rerun GN. By default it builds everything, but it also accepts target paths to build specific targets (see below). -- `fx clippy` runs Clippy on specific Rust targets (or all of them). - We use this in the Rust CI build to avoid running codegen on most Rust targets. - Underneath it invokes Ninja, just like `fx build`. - The clippy results are saved in json +- `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this + in the Rust CI build to avoid running codegen on most Rust targets. Underneath + it invokes Ninja, just like `fx build`. The clippy results are saved in json files inside the build output directory before being printed. #### Target paths @@ -86,20 +87,20 @@ GN uses paths like the following to identify build targets: //src/starnix/kernel:starnix_core ``` -The initial `//` means the root of the checkout, and the remaining slashes are directory names. -The string after `:` is the _target name_ of a target defined +The initial `//` means the root of the checkout, and the remaining slashes are +directory names. The string after `:` is the _target name_ of a target defined in the `BUILD.gn` file of that directory. -The target name can be omitted if it is the same as the directory name. -In other words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. +The target name can be omitted if it is the same as the directory name. In other +words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. These target paths are used inside `BUILD.gn` files to reference dependencies, and can also be used in `fx build`. #### Modifying compiler flags -You can put custom compiler flags inside a GN `config` that is added to a target. -As a simple example: +You can put custom compiler flags inside a GN `config` that is added to a +target. As a simple example: ``` config("everybody_loops") { @@ -113,20 +114,20 @@ rustc_binary("example") { } ``` -This will add the flag `-Zeverybody-loops` to rustc when building the `example` target. -Note that you can also use [`public_configs`] for a config to be added +This will add the flag `-Zeverybody-loops` to rustc when building the `example` +target. Note that you can also use [`public_configs`] for a config to be added to every target that depends on that target. -If you want to add a flag to every Rust target in the build, -you can add rustflags to the [`//build/config:compiler`] config or to the OS-specific -configs referenced in that file. -Note that `cflags` and `ldflags` are ignored on Rust targets. +If you want to add a flag to every Rust target in the build, you can add +rustflags to the [`//build/config:compiler`] config or to the OS-specific +configs referenced in that file. Note that `cflags` and `ldflags` are ignored on +Rust targets. #### Running ninja and rustc commands directly -Going down one layer, `fx build` invokes `ninja`, which in turn eventually invokes `rustc`. -All build actions are run inside the out directory, -which is usually `out/default` inside the Fuchsia checkout. +Going down one layer, `fx build` invokes `ninja`, which in turn eventually +invokes `rustc`. All build actions are run inside the out directory, which is +usually `out/default` inside the Fuchsia checkout. You can get ninja to print the actual command it invokes by forcing that command to fail, e.g. by adding a syntax error to one of the source files of the target. @@ -134,25 +135,26 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will -rebuild all the Rust targets. -This can be done in a text editor and the contents of the string do not matter, -as long as it changes from one build to the next. -[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. +rebuild all the Rust targets. This can be done in a text editor and the contents +of the string do not matter, as long as it changes from one build to the next. +[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain +directory. The Fuchsia website has more detailed documentation of the [build system]. #### Other tips and tricks When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` -command after the initial run so it won't rerun GN each time. -If you do this you can also comment out the version_string line to save a couple seconds. +command after the initial run so it won't rerun GN each time. If you do this you +can also comment out the version_string line to save a couple seconds. -`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. +`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the +initial build. ## Fuchsia target support -To learn more about Fuchsia target support, -see the Fuchsia chapter in [the rustc book][platform-support]. +To learn more about Fuchsia target support, see the Fuchsia chapter in [the +rustc book][platform-support]. [regressions]: https://gist.github.com/tmandry/7103eba4bd6a6fb0c439b5a90ae355fa [build-toolchain]: https://fuchsia.dev/fuchsia-src/development/build/rust_toolchain @@ -167,5 +169,5 @@ see the Fuchsia chapter in [the rustc book][platform-support]. [fuchsia-ping]: ../../notification-groups/fuchsia.md [^loc]: As of June 2024, Fuchsia had about 2 million lines of first-party Rust -code and a roughly equal amount of third-party code, -as counted by tokei (excluding comments and blanks). +code and a roughly equal amount of third-party code, as counted by tokei +(excluding comments and blanks). From cec94799dc959aea3b6bccde4fa8bbb9fbad0129 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Fri, 18 Sep 2026 17:49:10 +0000 Subject: [PATCH 69/76] Prepare for merging from rust-lang/rust This updates the rust-version file to 420ed2a0c3d7225b1744266fd884d431b4d8cfe0. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 7c357c0e50e54..1c3d6e0224718 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -c999cef531ea9059e189e82fe0e82c5daf249bc9 +420ed2a0c3d7225b1744266fd884d431b4d8cfe0 From 68638387ab79e7dc44623a3e1b029763c35fb1bd Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 19:53:49 +0200 Subject: [PATCH 70/76] improve closure.md --- src/doc/rustc-dev-guide/src/closure.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/closure.md b/src/doc/rustc-dev-guide/src/closure.md index 33206ed885bc5..81bddd73caba7 100644 --- a/src/doc/rustc-dev-guide/src/closure.md +++ b/src/doc/rustc-dev-guide/src/closure.md @@ -130,11 +130,11 @@ It borrows these upvars from its surrounding context; therefore the compiler has to determine the upvar's borrow type. The compiler starts with assigning an immutable borrow type and lowers the restriction (that is, changes it from **immutable** to **mutable** to **move**) as needed, based on the usage. -In the Example 1 above, the -closure only uses the variable for printing but does not modify it in any way and therefore, in the -`mir_dump`, we find the borrow type for the upvar `x` to be immutable. - In example 2, however, the closure modifies `x` and increments it by some value. - Because of this mutation, the compiler, which +In the Example 1 above, +the closure only uses the variable for printing but does not modify it in any way and therefore, +in the `mir_dump`, we find the borrow type for the upvar `x` to be immutable. +In example 2, however, the closure modifies `x` and increments it by some value. +Because of this mutation, the compiler, which started off assigning `x` as an immutable reference type, has to adjust it as a mutable reference. Likewise in the third example, the closure drops the vector and therefore this requires the variable `x` to be moved into the closure. From b8c15ce19e5154dddc6d1fbea1dd616d01a7a0d0 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 19:51:47 +0200 Subject: [PATCH 71/76] sembr src/tests/ecosystem-test-jobs/fuchsia.md --- .../src/tests/ecosystem-test-jobs/fuchsia.md | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index 3f274464c6e63..7fe06572650ad 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -14,12 +14,11 @@ Please contact the [fuchsia][fuchsia-ping] ping group and ask them for help. ## Building Fuchsia in CI -Fuchsia builds as part of the suite of bors tests that run before a pull request -is merged. +Fuchsia builds as part of the suite of bors tests that run before a pull request is merged. If you are worried that a pull request might break the Fuchsia builder and want -to test it out before submitting it to the bors queue, simply ask bors to run -the try job that builds the Fuchsia integration: +to test it out before submitting it to the bors queue, +simply ask bors to run the try job that builds the Fuchsia integration: ```text @bors try jobs=test-x86_64-fuchsia @@ -27,13 +26,13 @@ the try job that builds the Fuchsia integration: ## Building Fuchsia locally -Because Fuchsia uses languages other than Rust, it does not use Cargo as a build -system. It also requires the toolchain build to be configured in a [certain -way][build-toolchain]. +Because Fuchsia uses languages other than Rust, it does not use Cargo as a build system. +It also requires the toolchain build to be configured in a [certain way][build-toolchain]. The recommended way to build Fuchsia is to use the Docker scripts that check out -and run a Fuchsia build for you. If you've run Docker tests before, you can -simply run this command from your Rust checkout to download and build Fuchsia +and run a Fuchsia build for you. +If you've run Docker tests before, +you can simply run this command from your Rust checkout to download and build Fuchsia using your local Rust toolchain. ``` @@ -44,20 +43,21 @@ See the [Testing with Docker](../docker.md) chapter for more details on how to r and debug jobs with Docker. Note that a Fuchsia checkout is *large* – as of this writing, a checkout and -build takes over 67G of space – and as you might imagine, it takes a while to -complete. +build takes over 67G of space – and as you might imagine, it takes a while to complete. ### Modifying the Fuchsia checkout The main reason you would want to build Fuchsia locally is because you need to -investigate a regression. After running a Docker build, you'll find the Fuchsia -checkout inside the `obj/test-x86_64-fuchsia/fuchsia` directory of your Rust -checkout. If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] -script to `KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun -the build command above. This will reuse all the build results from before. +investigate a regression. +After running a Docker build, +you'll find the Fuchsia checkout inside the `obj/test-x86_64-fuchsia/fuchsia` directory of your Rust +checkout. + If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to `KEEP_CHECKOUT=1`, +you can change the checkout as needed and rerun +the build command above. +This will reuse all the build results from before. -You can find more options to customize the Fuchsia checkout in the -[build-fuchsia.sh] script. +You can find more options to customize the Fuchsia checkout in the [build-fuchsia.sh] script. ### Customizing the Fuchsia build @@ -73,14 +73,15 @@ to add this to your `$PATH` for some workflows. There are a few `fx` subcommands that are relevant, including: -- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and - runs GN. -- `fx build` builds the Fuchsia project using Ninja. It will automatically pick - up changes to build arguments and rerun GN. By default it builds everything, +- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. +- `fx build` builds the Fuchsia project using Ninja. + It will automatically pick up changes to build arguments and rerun GN. + By default it builds everything, but it also accepts target paths to build specific targets (see below). -- `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this - in the Rust CI build to avoid running codegen on most Rust targets. Underneath - it invokes Ninja, just like `fx build`. The clippy results are saved in json +- `fx clippy` runs Clippy on specific Rust targets (or all of them). + We use this in the Rust CI build to avoid running codegen on most Rust targets. + Underneath it invokes Ninja, just like `fx build`. + The clippy results are saved in json files inside the build output directory before being printed. #### Target paths @@ -91,20 +92,20 @@ GN uses paths like the following to identify build targets: //src/starnix/kernel:starnix_core ``` -The initial `//` means the root of the checkout, and the remaining slashes are -directory names. The string after `:` is the _target name_ of a target defined +The initial `//` means the root of the checkout, and the remaining slashes are directory names. +The string after `:` is the _target name_ of a target defined in the `BUILD.gn` file of that directory. -The target name can be omitted if it is the same as the directory name. In other -words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. +The target name can be omitted if it is the same as the directory name. +In other words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. These target paths are used inside `BUILD.gn` files to reference dependencies, and can also be used in `fx build`. #### Modifying compiler flags -You can put custom compiler flags inside a GN `config` that is added to a -target. As a simple example: +You can put custom compiler flags inside a GN `config` that is added to a target. +As a simple example: ``` config("everybody_loops") { @@ -118,20 +119,20 @@ rustc_binary("example") { } ``` -This will add the flag `-Zeverybody-loops` to rustc when building the `example` -target. Note that you can also use [`public_configs`] for a config to be added +This will add the flag `-Zeverybody-loops` to rustc when building the `example` target. +Note that you can also use [`public_configs`] for a config to be added to every target that depends on that target. -If you want to add a flag to every Rust target in the build, you can add -rustflags to the [`//build/config:compiler`] config or to the OS-specific -configs referenced in that file. Note that `cflags` and `ldflags` are ignored on -Rust targets. +If you want to add a flag to every Rust target in the build, +you can add rustflags to the [`//build/config:compiler`] config or to the OS-specific +configs referenced in that file. +Note that `cflags` and `ldflags` are ignored on Rust targets. #### Running ninja and rustc commands directly -Going down one layer, `fx build` invokes `ninja`, which in turn eventually -invokes `rustc`. All build actions are run inside the out directory, which is -usually `out/default` inside the Fuchsia checkout. +Going down one layer, `fx build` invokes `ninja`, which in turn eventually invokes `rustc`. +All build actions are run inside the out directory, +which is usually `out/default` inside the Fuchsia checkout. You can get ninja to print the actual command it invokes by forcing that command to fail, e.g. by adding a syntax error to one of the source files of the target. @@ -139,26 +140,25 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will -rebuild all the Rust targets. This can be done in a text editor and the contents -of the string do not matter, as long as it changes from one build to the next. -[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain -directory. +rebuild all the Rust targets. +This can be done in a text editor and the contents of the string do not matter, +as long as it changes from one build to the next. +[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. The Fuchsia website has more detailed documentation of the [build system]. #### Other tips and tricks When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` -command after the initial run so it won't rerun GN each time. If you do this you -can also comment out the version_string line to save a couple seconds. +command after the initial run so it won't rerun GN each time. +If you do this you can also comment out the version_string line to save a couple seconds. -`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the -initial build. +`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. ## Fuchsia target support -To learn more about Fuchsia target support, see the Fuchsia chapter in [the -rustc book][platform-support]. +To learn more about Fuchsia target support, +see the Fuchsia chapter in [the rustc book][platform-support]. [regressions]: https://gist.github.com/tmandry/7103eba4bd6a6fb0c439b5a90ae355fa [build-toolchain]: https://fuchsia.dev/fuchsia-src/development/build/rust_toolchain @@ -173,5 +173,5 @@ rustc book][platform-support]. [fuchsia-ping]: ../../notification-groups/fuchsia.md [^loc]: As of June 2024, Fuchsia had about 2 million lines of first-party Rust -code and a roughly equal amount of third-party code, as counted by tokei -(excluding comments and blanks). +code and a roughly equal amount of third-party code, +as counted by tokei (excluding comments and blanks). From d9d1b3e6ed9ebfd6a2b4a7199afa42e9a35ef11a Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Fri, 18 Sep 2026 19:59:47 +0200 Subject: [PATCH 72/76] improve tests/ecosystem-test-jobs/fuchsia.md --- .../src/tests/ecosystem-test-jobs/fuchsia.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index 7fe06572650ad..ca7a9f482f1ae 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -52,7 +52,7 @@ investigate a regression. After running a Docker build, you'll find the Fuchsia checkout inside the `obj/test-x86_64-fuchsia/fuchsia` directory of your Rust checkout. - If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to `KEEP_CHECKOUT=1`, +If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to `KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun the build command above. This will reuse all the build results from before. @@ -76,11 +76,11 @@ There are a few `fx` subcommands that are relevant, including: - `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. - `fx build` builds the Fuchsia project using Ninja. It will automatically pick up changes to build arguments and rerun GN. - By default it builds everything, + By default, it builds everything, but it also accepts target paths to build specific targets (see below). - `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this in the Rust CI build to avoid running codegen on most Rust targets. - Underneath it invokes Ninja, just like `fx build`. + Underneath, it invokes Ninja, just like `fx build`. The clippy results are saved in json files inside the build output directory before being printed. @@ -141,7 +141,7 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will rebuild all the Rust targets. -This can be done in a text editor and the contents of the string do not matter, +This can be done in a text editor, and the contents of the string do not matter, as long as it changes from one build to the next. [build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. @@ -151,7 +151,7 @@ The Fuchsia website has more detailed documentation of the [build system]. When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` command after the initial run so it won't rerun GN each time. -If you do this you can also comment out the version_string line to save a couple seconds. +If you do this, you can also comment out the version_string line to save a couple seconds. `export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. From b7bad07da0564a861c8ffcbeb290d4409f5cb937 Mon Sep 17 00:00:00 2001 From: beetrees Date: Tue, 25 Aug 2026 18:26:24 +0100 Subject: [PATCH 73/76] Add Natvis visualiser and debuginfo tests for `f128` --- .../src/debuginfo/metadata.rs | 101 ++++++++-------- src/etc/lldb_lookup.py | 17 +++ src/etc/lldb_providers.py | 6 + src/etc/natvis/intrinsic.natvis | 112 ++++++++++++++++++ .../debuginfo/basic-types-globals-metadata.rs | 10 +- tests/debuginfo/basic-types-globals.rs | 24 +++- tests/debuginfo/basic-types-metadata.rs | 4 +- tests/debuginfo/basic-types-mut-globals.rs | 96 +++++++++++++-- tests/debuginfo/basic-types/main.rs | 13 +- tests/debuginfo/borrowed-basic.rs | 17 ++- tests/debuginfo/borrowed-unique-basic.rs | 17 ++- tests/debuginfo/f128-natvis.rs | 92 ++++++++++++++ tests/debuginfo/reference-debuginfo.rs | 18 ++- 13 files changed, 447 insertions(+), 80 deletions(-) create mode 100644 tests/debuginfo/f128-natvis.rs diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index 54bdfb5f442d9..53fe51e382e4e 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::{assert_matches, iter, ptr}; use libc::{c_longlong, c_uint}; -use rustc_abi::{Align, Layout, NumScalableVectors, Size}; +use rustc_abi::{Align, Endian, Layout, NumScalableVectors, Size}; use rustc_codegen_ssa::debuginfo::type_names::{VTableNameKind, cpp_like_debuginfo}; use rustc_codegen_ssa::traits::*; use rustc_hir::def::{CtorKind, DefKind}; @@ -20,7 +20,7 @@ use rustc_middle::ty::{ use rustc_session::config::{self, DebugInfo, Lto}; use rustc_span::{DUMMY_SP, FileName, RemapPathScopeComponents, SourceFile, Span, Symbol, hygiene}; use rustc_symbol_mangling::typeid_for_trait_ref; -use rustc_target::spec::{Arch, DebuginfoKind}; +use rustc_target::spec::{Arch, DebuginfoKind, HasTargetSpec}; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -692,33 +692,22 @@ impl MsvcBasicName for ty::UintTy { } } -impl MsvcBasicName for ty::FloatTy { - fn msvc_basic_name(self) -> &'static str { - // FIXME(f128): `f128` has no MSVC representation. We could improve the debuginfo. - // See: - match self { - ty::FloatTy::F16 => { - bug!("`f16` should have been handled in `build_basic_type_di_node`") - } - ty::FloatTy::F32 => "float", - ty::FloatTy::F64 => "double", - ty::FloatTy::F128 => "fp128", - } - } -} - -fn build_cpp_f16_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) -> DINodeCreationResult<'ll> { - // MSVC has no native support for `f16`. Instead, emit `struct f16 { bits: u16 }` to allow the - // `f16`'s value to be displayed using a Natvis visualiser in `intrinsic.natvis`. - let float_ty = cx.tcx.types.f16; - let bits_ty = cx.tcx.types.u16; - let def_location = if cx.sess().opts.unstable_opts.debug_info_type_line_numbers { - match float_ty.kind() { - ty::Adt(def, _) => Some(file_metadata_from_def_id(cx, Some(def.did()))), - _ => None, - } +/// `float_ty` must be a [`ty::Float`] and `bits_ty` must be a [`ty::Uint`]. +/// `cx.size_of(bits_ty) * bits_names.len()` must equal `cx.size_of(float_ty)`. +fn build_cpp_float_struct_di_node<'ll, 'tcx>( + cx: &CodegenCx<'ll, 'tcx>, + float_ty: Ty<'tcx>, + bits_ty: Ty<'tcx>, + bits_names: &[&str], +) -> DINodeCreationResult<'ll> { + debug_assert!(matches!(bits_ty.kind(), ty::Uint(_))); + debug_assert_eq!(cx.size_of(bits_ty) * (bits_names.len() as u64), cx.size_of(float_ty)); + // MSVC has no native support for `f16` or `f128`. Instead, emit a struct containing the bits as + // field(s) to allow the value to be displayed using a Natvis visualiser in `intrinsic.natvis`. + let name = if let ty::Float(f) = float_ty.kind() { + f.name_str() } else { - None + bug!("{float_ty:?} was not a float"); }; type_map::build_type_with_children( cx, @@ -726,32 +715,33 @@ fn build_cpp_f16_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) -> DINodeCreation cx, Stub::Struct, UniqueTypeId::for_ty(cx.tcx, float_ty), - "f16", - def_location, + name, + None, cx.size_and_align_of(float_ty), NO_SCOPE_METADATA, DIFlags::FlagZero, ), // Fields: |cx, float_di_node| { - let def_id = if cx.sess().opts.unstable_opts.debug_info_type_line_numbers { - match bits_ty.kind() { - ty::Adt(def, _) => Some(def.did()), - _ => None, - } - } else { - None - }; - smallvec![build_field_di_node( - cx, - float_di_node, - "bits", - cx.layout_of(bits_ty), - Size::ZERO, - DIFlags::FlagZero, - type_di_node(cx, bits_ty), - def_id, - )] + let bits_layout = cx.layout_of(bits_ty); + let bits_node = type_di_node(cx, bits_ty); + bits_names + .iter() + .copied() + .enumerate() + .map(|(i, field_name)| { + build_field_di_node( + cx, + float_di_node, + field_name, + bits_layout, + bits_layout.size * (i as u64), + DIFlags::FlagZero, + bits_node, + None, + ) + }) + .collect() }, NO_GENERICS, ) @@ -783,9 +773,20 @@ fn build_basic_type_di_node<'ll, 'tcx>( ty::Int(int_ty) if cpp_like_debuginfo => (int_ty.msvc_basic_name(), DW_ATE_signed), ty::Uint(uint_ty) if cpp_like_debuginfo => (uint_ty.msvc_basic_name(), DW_ATE_unsigned), ty::Float(ty::FloatTy::F16) if cpp_like_debuginfo => { - return build_cpp_f16_di_node(cx); + return build_cpp_float_struct_di_node(cx, t, cx.tcx.types.u16, &["bits"]); + } + ty::Float(ty::FloatTy::F128) if cpp_like_debuginfo => { + // All MSVC architectures are little endian. + assert_eq!(cx.target_spec().endian, Endian::Little); + return build_cpp_float_struct_di_node( + cx, + t, + cx.tcx.types.u64, + &["low_bits", "high_bits"], + ); } - ty::Float(float_ty) if cpp_like_debuginfo => (float_ty.msvc_basic_name(), DW_ATE_float), + ty::Float(ty::FloatTy::F32) if cpp_like_debuginfo => ("float", DW_ATE_float), + ty::Float(ty::FloatTy::F64) if cpp_like_debuginfo => ("double", DW_ATE_float), ty::Int(int_ty) => (int_ty.name_str(), DW_ATE_signed), ty::Uint(uint_ty) => (uint_ty.name_str(), DW_ATE_unsigned), ty::Float(float_ty) => (float_ty.name_str(), DW_ATE_float), diff --git a/src/etc/lldb_lookup.py b/src/etc/lldb_lookup.py index 365816dc8489a..94ed47af1891f 100644 --- a/src/etc/lldb_lookup.py +++ b/src/etc/lldb_lookup.py @@ -40,6 +40,7 @@ ClangEncodedEnumSummaryProvider, StructSummaryProvider, f16SummaryProvider, + f128SummaryProvider, # re-exports get_template_args as get_template_args, resolve_msvc_template_arg as resolve_msvc_template_arg, @@ -181,6 +182,17 @@ def register_providers_compatibility(): DEFAULT_TYPE_OPTIONS | lldb.eTypeOptionHideChildren, ) + if LLDBFeature.Float128 in FEATURE_FLAGS: + # Force f128 summary on windows-msvc since most Windows debuggers don't support PDB f128 + register_summary( + f128SummaryProvider, + lldb.SBTypeNameSpecifier( + MOD_PREFIX + is_msvc_f128.__name__, + lldb.eFormatterMatchCallback, + ), + DEFAULT_TYPE_OPTIONS | lldb.eTypeOptionHideChildren, + ) + # Tuple-structs register_synth( TupleSyntheticProvider, @@ -501,6 +513,11 @@ def is_msvc_f16(type: lldb.SBType, _dict: LLDBOpaque) -> bool: return type.GetName() == "f16" and type.IsAggregateType() +def is_msvc_f128(type: lldb.SBType, _dict: LLDBOpaque) -> bool: + # Most Windows debuggers don't support PDB f128. + return type.GetName() == "f128" and type.IsAggregateType() + + def classify_rust_type(type: lldb.SBType, is_msvc: bool) -> RustType: if type.IsPointerType(): return RustType.Indirection diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index 2791dae3600b0..a3a424ac1abce 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -542,6 +542,12 @@ def f16SummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: ) +def f128SummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: + from lldb import eBasicTypeFloat128 + + return valobj.Cast(valobj.GetTarget().GetBasicType(eBasicTypeFloat128)).GetValue() + + def sequence_formatter(output: str, valobj: SBValue, _dict: LLDBOpaque): length: int = valobj.GetNumChildren() diff --git a/src/etc/natvis/intrinsic.natvis b/src/etc/natvis/intrinsic.natvis index 49e0ce319efac..ac9bf1c427957 100644 --- a/src/etc/natvis/intrinsic.natvis +++ b/src/etc/natvis/intrinsic.natvis @@ -59,6 +59,118 @@ {(float) (sign() * (raw_significand() + 1.0) * two_pow_exponent())} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {sign()}inf + NaN + + {sign()}0x0p+0 + + {sign()}0x1{subnormal_hex()}p{-16382 - subnormal_shift(),d} + {sign()}0x1{normal_hex()}p{normal_exponent_sign()}{normal_exponent(),d} + + + "0x" + hex128(high_bits, low_bits, 128) + + () diff --git a/tests/debuginfo/basic-types-globals-metadata.rs b/tests/debuginfo/basic-types-globals-metadata.rs index 3f1d9fd5de278..f7f3504ae26fa 100644 --- a/tests/debuginfo/basic-types-globals-metadata.rs +++ b/tests/debuginfo/basic-types-globals-metadata.rs @@ -33,11 +33,12 @@ //@ gdb-check:type = f32 //@ gdb-command:whatis basic_types_globals_metadata::F64 //@ gdb-check:type = f64 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:continue #![allow(unused_variables)] #![allow(dead_code)] -#![feature(f16)] +#![feature(f16, f128)] // N.B. These are `mut` only so they don't constant fold away. static mut B: bool = false; @@ -55,13 +56,14 @@ static mut U64: u64 = 64; static mut F16: f16 = 1.5; static mut F32: f32 = 2.5; static mut F64: f64 = 3.5; +static mut F128: f128 = 4.5; fn main() { _zzz(); // #break - let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64) }; - // FIXME: Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which - // does not exist on some targets like PowerPC. + let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64, F128) }; + // FIXME(f16): Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which + // does not exist on some targets like PowerPC (fixed in llvm22). // See https://github.com/llvm/llvm-project/issues/97981 and // https://github.com/rust-lang/compiler-builtins/issues/655 let b = unsafe { F16 }; diff --git a/tests/debuginfo/basic-types-globals.rs b/tests/debuginfo/basic-types-globals.rs index 044b757aaf470..3bc9d3becdade 100644 --- a/tests/debuginfo/basic-types-globals.rs +++ b/tests/debuginfo/basic-types-globals.rs @@ -1,11 +1,20 @@ -//@ revisions: lto no-lto +//@ revisions: lto no-lto lto-apple no-lto-apple //@ compile-flags:-g --crate-name=basic_types_globals //@ disable-gdb-pretty-printers +// FIXME(f128): Merge `-apple` revisions once Apple releases Xcode with LLVM 22. +//@ [lto] ignore-apple +//@ [no-lto] ignore-apple +//@ [lto-apple] only-apple +//@ [no-lto-apple] only-apple //@ [lto] compile-flags:-C lto //@ [lto] no-prefer-dynamic +//@ [lto-apple] compile-flags:-C lto +//@ [lto-apple] no-prefer-dynamic //@ ignore-backends: gcc +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 //@ lldb-command:run //@ lldb-command:v basic_types_globals::B @@ -38,6 +47,9 @@ //@ lldb-check:[...]basic_types_globals::F32 = 2.5 //@ lldb-command:v basic_types_globals::F64 //@ lldb-check:[...]basic_types_globals::F64 = 3.5 +//@ lldb-command:v basic_types_globals::F128 +//@[no-lto] lldb-check:[...]basic_types_globals::F128 = 4.5 +//@[lto] lldb-check:[...]basic_types_globals::F128 = 4.5 //@ gdb-command:run //@ gdb-command:print B @@ -70,10 +82,11 @@ //@ gdb-check:$14 = 2.5 //@ gdb-command:print F64 //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:continue #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] // N.B. These are `mut` only so they don't constant fold away. static mut B: bool = false; @@ -91,13 +104,14 @@ static mut U64: u64 = 64; static mut F16: f16 = 1.5; static mut F32: f32 = 2.5; static mut F64: f64 = 3.5; +static mut F128: f128 = 4.5; fn main() { _zzz(); // #break - let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64) }; - // FIXME: Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which - // does not exist on some targets like PowerPC. + let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64, F128) }; + // FIXME(f16): Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which + // does not exist on some targets like PowerPC (fixed in llvm22). // See https://github.com/llvm/llvm-project/issues/97981 and // https://github.com/rust-lang/compiler-builtins/issues/655 let b = unsafe { F16 }; diff --git a/tests/debuginfo/basic-types-metadata.rs b/tests/debuginfo/basic-types-metadata.rs index d3a3d03ef7424..7171840077265 100644 --- a/tests/debuginfo/basic-types-metadata.rs +++ b/tests/debuginfo/basic-types-metadata.rs @@ -35,6 +35,7 @@ //@ gdb-check:type = f32 //@ gdb-command:whatis f64 //@ gdb-check:type = f64 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:whatis fnptr //@ gdb-check:type = *mut fn () //@ gdb-command:info functions _yyy @@ -54,7 +55,7 @@ //@ gdb-command:continue #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let unit: () = (); @@ -73,6 +74,7 @@ fn main() { let f16: f16 = 1.5; let f32: f32 = 2.5; let f64: f64 = 3.5; + let f128: f128 = 4.5; let fnptr : fn() = _zzz; let closure_0 = || {}; let closure_1 = || { b; }; diff --git a/tests/debuginfo/basic-types-mut-globals.rs b/tests/debuginfo/basic-types-mut-globals.rs index c3cc7be549d47..3f59da2a5d2e0 100644 --- a/tests/debuginfo/basic-types-mut-globals.rs +++ b/tests/debuginfo/basic-types-mut-globals.rs @@ -1,13 +1,14 @@ -// Caveats - gdb prints any 8-bit value (meaning rust I8 and u8 values) -// as its numerical value along with its associated ASCII char, there -// doesn't seem to be any way around this. Also, gdb doesn't know -// about UTF-32 character encoding and will print a rust char as only -// its numerical value. - -//@ compile-flags:-g +//@ compile-flags:-g --crate-name=basic_types_mut_globals //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + //@ gdb-command:run // Check initializers @@ -41,6 +42,7 @@ //@ gdb-check:$14 = 2.5 //@ gdb-command:print F64 //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:continue // Check new values @@ -50,7 +52,7 @@ //@ gdb-check:$17 = 2 //@ gdb-command:print C //@ gdb-check:$18 = 102 'f' -//@ gdb-command:print/d I8 +//@ gdb-command:print I8 //@ gdb-check:$19 = 78 //@ gdb-command:print I16 //@ gdb-check:$20 = -26 @@ -60,7 +62,7 @@ //@ gdb-check:$22 = -54 //@ gdb-command:print U //@ gdb-check:$23 = 5 -//@ gdb-command:print/d U8 +//@ gdb-command:print U8 //@ gdb-check:$24 = 20 //@ gdb-command:print U16 //@ gdb-check:$25 = 32 @@ -74,9 +76,81 @@ //@ gdb-check:$29 = 5.75 //@ gdb-command:print F64 //@ gdb-check:$30 = 9.25 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + +//@ lldb-command:run + +// Check initializers +//@ lldb-command:v basic_types_mut_globals::B +//@ lldb-check:[...]basic_types_mut_globals::B = false +//@ lldb-command:v basic_types_mut_globals::I +//@ lldb-check:[...]basic_types_mut_globals::I = -1 +//@ lldb-command:v basic_types_mut_globals::C +//@ lldb-check:[...]basic_types_mut_globals::C = U+0x00000061 U'a' +//@ lldb-command:v/d basic_types_mut_globals::I8 +//@ lldb-check:[...]basic_types_mut_globals::I8 = 68 +//@ lldb-command:v basic_types_mut_globals::I16 +//@ lldb-check:[...]basic_types_mut_globals::I16 = -16 +//@ lldb-command:v basic_types_mut_globals::I32 +//@ lldb-check:[...]basic_types_mut_globals::I32 = -32 +//@ lldb-command:v basic_types_mut_globals::I64 +//@ lldb-check:[...]basic_types_mut_globals::I64 = -64 +//@ lldb-command:v basic_types_mut_globals::U +//@ lldb-check:[...]basic_types_mut_globals::U = 1 +//@ lldb-command:v/d basic_types_mut_globals::U8 +//@ lldb-check:[...]basic_types_mut_globals::U8 = 100 +//@ lldb-command:v basic_types_mut_globals::U16 +//@ lldb-check:[...]basic_types_mut_globals::U16 = 16 +//@ lldb-command:v basic_types_mut_globals::U32 +//@ lldb-check:[...]basic_types_mut_globals::U32 = 32 +//@ lldb-command:v basic_types_mut_globals::U64 +//@ lldb-check:[...]basic_types_mut_globals::U64 = 64 +//@ lldb-command:v basic_types_mut_globals::F16 +//@ lldb-check:[...]basic_types_mut_globals::F16 = 1.5 +//@ lldb-command:v basic_types_mut_globals::F32 +//@ lldb-check:[...]basic_types_mut_globals::F32 = 2.5 +//@ lldb-command:v basic_types_mut_globals::F64 +//@ lldb-check:[...]basic_types_mut_globals::F64 = 3.5 +//@ lldb-command:v basic_types_mut_globals::F128 +//@[not-apple] lldb-check:[...]basic_types_mut_globals::F128 = 4.5 +//@ lldb-command:continue + +// Check new values +//@ lldb-command:v basic_types_mut_globals::B +//@ lldb-check:[...]basic_types_mut_globals::B = true +//@ lldb-command:v basic_types_mut_globals::I +//@ lldb-check:[...]basic_types_mut_globals::I = 2 +//@ lldb-command:v basic_types_mut_globals::C +//@ lldb-check:[...]basic_types_mut_globals::C = U+0x00000066 U'f' +//@ lldb-command:v/d basic_types_mut_globals::I8 +//@ lldb-check:[...]basic_types_mut_globals::I8 = 78 +//@ lldb-command:v basic_types_mut_globals::I16 +//@ lldb-check:[...]basic_types_mut_globals::I16 = -26 +//@ lldb-command:v basic_types_mut_globals::I32 +//@ lldb-check:[...]basic_types_mut_globals::I32 = -12 +//@ lldb-command:v basic_types_mut_globals::I64 +//@ lldb-check:[...]basic_types_mut_globals::I64 = -54 +//@ lldb-command:v basic_types_mut_globals::U +//@ lldb-check:[...]basic_types_mut_globals::U = 5 +//@ lldb-command:v/d basic_types_mut_globals::U8 +//@ lldb-check:[...]basic_types_mut_globals::U8 = 20 +//@ lldb-command:v basic_types_mut_globals::U16 +//@ lldb-check:[...]basic_types_mut_globals::U16 = 32 +//@ lldb-command:v basic_types_mut_globals::U32 +//@ lldb-check:[...]basic_types_mut_globals::U32 = 16 +//@ lldb-command:v basic_types_mut_globals::U64 +//@ lldb-check:[...]basic_types_mut_globals::U64 = 128 +//@ lldb-command:v basic_types_mut_globals::F16 +//@ lldb-check:[...]basic_types_mut_globals::F16 = 2.25 +//@ lldb-command:v basic_types_mut_globals::F32 +//@ lldb-check:[...]basic_types_mut_globals::F32 = 5.75 +//@ lldb-command:v basic_types_mut_globals::F64 +//@ lldb-check:[...]basic_types_mut_globals::F64 = 9.25 +//@ lldb-command:v basic_types_mut_globals::F128 +//@[not-apple] lldb-check:[...]basic_types_mut_globals::F128 = 12.75 #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] static mut B: bool = false; static mut I: isize = -1; @@ -93,6 +167,7 @@ static mut U64: u64 = 64; static mut F16: f16 = 1.5; static mut F32: f32 = 2.5; static mut F64: f64 = 3.5; +static mut F128: f128 = 4.5; fn main() { _zzz(); // #break @@ -113,6 +188,7 @@ fn main() { F16 = 2.25; F32 = 5.75; F64 = 9.25; + F128 = 12.75; } _zzz(); // #break diff --git a/tests/debuginfo/basic-types/main.rs b/tests/debuginfo/basic-types/main.rs index 9f61862c0dfd8..d01e51036f201 100644 --- a/tests/debuginfo/basic-types/main.rs +++ b/tests/debuginfo/basic-types/main.rs @@ -1,9 +1,3 @@ -// Caveats - gdb prints any 8-bit value (meaning rust i8 and u8 values) -// as its numerical value along with its associated ASCII char, there -// doesn't seem to be any way around this. Also, gdb doesn't know -// about UTF-32 character encoding and will print a rust char as only -// its numerical value. - //@ compile-flags:-g //@ disable-gdb-pretty-printers //@ ignore-backends: gcc @@ -32,6 +26,7 @@ //@ gdb-repr:f16 //@ gdb-repr:f32 //@ gdb-repr:f64 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-repr:s // === LLDB TESTS ================================================================================== @@ -85,13 +80,16 @@ //@ cdb-check:f32 : 2.500000 [Type: float] //@ cdb-command:dx f64 //@ cdb-check:f64 : 3.500000 [Type: double] +//@ cdb-command:dx f128 +//@ cdb-check:f128 : 0x1.2p+2 [Type: f128] +//@ cdb-check:bits : 0x40012000000000000000000000000000 //@ cdb-command:.enable_unicode 1 // FIXME(#88840): The latest version of the Windows SDK broke the visualizer for str. //@ cdb-command:dx s //@ cdb-check:s : [...] [Type: ref$] #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let b: bool = false; @@ -109,6 +107,7 @@ fn main() { let f16: f16 = 1.5; let f32: f32 = 2.5; let f64: f64 = 3.5; + let f128: f128 = 4.5; let s: &str = "Hello, World!"; _zzz(); // #break } diff --git a/tests/debuginfo/borrowed-basic.rs b/tests/debuginfo/borrowed-basic.rs index f7b7d2cbd810c..2872bc65fac3f 100644 --- a/tests/debuginfo/borrowed-basic.rs +++ b/tests/debuginfo/borrowed-basic.rs @@ -2,6 +2,13 @@ //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + // === GDB TESTS =================================================================================== //@ gdb-command:run @@ -50,6 +57,8 @@ //@ gdb-command:print *f64_ref //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + // === LLDB TESTS ================================================================================== @@ -99,8 +108,11 @@ //@ lldb-command:v *f64_ref //@ lldb-check:[...] 3.5 +//@ lldb-command:v *f128_ref +//@[not-apple] lldb-check:[...] 4.5 + #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let bool_val: bool = true; @@ -148,6 +160,9 @@ fn main() { let f64_val: f64 = 3.5; let f64_ref: &f64 = &f64_val; + let f128_val: f128 = 4.5; + let f128_ref: &f128 = &f128_val; + zzz(); // #break } diff --git a/tests/debuginfo/borrowed-unique-basic.rs b/tests/debuginfo/borrowed-unique-basic.rs index 17939239c0dea..0d1fdec0f58d1 100644 --- a/tests/debuginfo/borrowed-unique-basic.rs +++ b/tests/debuginfo/borrowed-unique-basic.rs @@ -2,6 +2,13 @@ //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + // === GDB TESTS =================================================================================== //@ gdb-command:run @@ -51,6 +58,8 @@ //@ gdb-command:print *f64_ref //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + // === LLDB TESTS ================================================================================== @@ -102,8 +111,11 @@ //@ lldb-command:v *f64_ref //@ lldb-check:[...] 3.5 +//@ lldb-command:v *f128_ref +//@[not-apple] lldb-check:[...] 4.5 + #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let bool_box: Box = Box::new(true); @@ -151,6 +163,9 @@ fn main() { let f64_box: Box = Box::new(3.5); let f64_ref: &f64 = &*f64_box; + let f128_box: Box = Box::new(4.5); + let f128_ref: &f128 = &*f128_box; + zzz(); // #break } diff --git a/tests/debuginfo/f128-natvis.rs b/tests/debuginfo/f128-natvis.rs new file mode 100644 index 0000000000000..5f91014cfe5db --- /dev/null +++ b/tests/debuginfo/f128-natvis.rs @@ -0,0 +1,92 @@ +//@ compile-flags: -g +//@ only-msvc + +// This tests the `f128` Natvis visualiser. +//@ cdb-command:g +//@ cdb-command:dx v0_0 +//@ cdb-check:v0_0 : 0x0p+0 [Type: f128] +//@ cdb-check:bits : 0x00000000000000000000000000000000 +//@ cdb-command:dx neg_0_0 +//@ cdb-check:neg_0_0 : -0x0p+0 [Type: f128] +//@ cdb-check:bits : 0x80000000000000000000000000000000 +//@ cdb-command:dx v1_0 +//@ cdb-check:v1_0 : 0x1p+0 [Type: f128] +//@ cdb-check:bits : 0x3fff0000000000000000000000000000 +//@ cdb-command:dx v1_5 +//@ cdb-check:v1_5 : 0x1.8p+0 [Type: f128] +//@ cdb-check:bits : 0x3fff8000000000000000000000000000 +//@ cdb-command:dx v72_3 +//@ cdb-check:v72_3 : 0x1.2133333333333333333333333333p+6 [Type: f128] +//@ cdb-check:bits : 0x40052133333333333333333333333333 +//@ cdb-command:dx neg_0_126 +//@ cdb-check:neg_0_126 : -0x1.020c49ba5e353f7ced916872b021p-3 [Type: f128] +//@ cdb-check:bits : 0xbffc020c49ba5e353f7ced916872b021 +//@ cdb-command:dx v0_00003 +//@ cdb-check:v0_00003 : 0x1.f75104d551d68c692f6e82949a56p-16 [Type: f128] +//@ cdb-check:bits : 0x3feff75104d551d68c692f6e82949a56 +//@ cdb-command:dx neg_0_00004 +//@ cdb-check:neg_0_00004 : -0x1.4f8b588e368f08461f9f01b866e4p-15 [Type: f128] +//@ cdb-check:bits : 0xbff04f8b588e368f08461f9f01b866e4 +//@ cdb-command:dx very_small +//@ cdb-check:very_small : 0x1p-16494 [Type: f128] +//@ cdb-check:bits : 0x00000000000000000000000000000001 +//@ cdb-command:dx not_quite_as_small +//@ cdb-check:not_quite_as_small : 0x1.8p-16385 [Type: f128] +//@ cdb-check:bits : 0x00003000000000000000000000000000 +//@ cdb-command:dx smallest_pos_normal +//@ cdb-check:smallest_pos_normal : 0x1p-16382 [Type: f128] +//@ cdb-check:bits : 0x00010000000000000000000000000000 +//@ cdb-command:dx smallest_subnormal +//@ cdb-check:smallest_subnormal : -0x1.fffffffffffffffffffffffffffep-16383 [Type: f128] +//@ cdb-check:bits : 0x8000ffffffffffffffffffffffffffff +//@ cdb-command:dx just_above +//@ cdb-check:just_above : -0x1.ffffffffffffffffffffffffff8p-1 [Type: f128] +//@ cdb-check:bits : 0xbffeffffffffffffffffffffffffff80 +//@ cdb-command:dx max +//@ cdb-check:max : 0x1.ffffffffffffffffffffffffffffp+16383 [Type: f128] +//@ cdb-check:bits : 0x7ffeffffffffffffffffffffffffffff +//@ cdb-command:dx min +//@ cdb-check:min : -0x1.ffffffffffffffffffffffffffffp+16383 [Type: f128] +//@ cdb-check:bits : 0xfffeffffffffffffffffffffffffffff +//@ cdb-command:dx inf +//@ cdb-check:inf : inf [Type: f128] +//@ cdb-check:bits : 0x7fff0000000000000000000000000000 +//@ cdb-command:dx neg_inf +//@ cdb-check:neg_inf : -inf [Type: f128] +//@ cdb-check:bits : 0xffff0000000000000000000000000000 +//@ cdb-command:dx nan +//@ cdb-check:nan : NaN [Type: f128] +//@ cdb-check:bits : 0x7fff8000000000000000000000000000 +//@ cdb-command:dx other_nan +//@ cdb-check:other_nan : NaN [Type: f128] +//@ cdb-check:bits : 0xffff123456789abcdef123456789abcd + +#![feature(f128)] + +fn main() { + let v0_0 = 0.0_f128; + let neg_0_0 = -0.0_f128; + let v1_0 = 1.0_f128; + let v1_5 = 1.5_f128; + let v72_3 = 72.3_f128; + let neg_0_126 = -0.126_f128; + let v0_00003 = 0.00003_f128; + let neg_0_00004 = -0.00004_f128; + let very_small = 0.0_f128.next_up(); + let not_quite_as_small = const { f128::MIN_POSITIVE / 8.0 + f128::MIN_POSITIVE / 16.0 }; + let smallest_pos_normal = f128::MIN_POSITIVE; + let smallest_subnormal = (-f128::MIN_POSITIVE).next_up(); + let just_above = const { -1.0 + f128::EPSILON * 64.0 }; + let max = f128::MAX; + let min = f128::MIN; + let inf = f128::INFINITY; + let neg_inf = f128::NEG_INFINITY; + let nan = f128::NAN; + let other_nan = f128::from_bits(0xffff_1234_5678_9abc_def1_2345_6789_abcd); + + _zzz(); // #break +} + +fn _zzz() { + () +} diff --git a/tests/debuginfo/reference-debuginfo.rs b/tests/debuginfo/reference-debuginfo.rs index 518e1dac2885e..495dde379da7e 100644 --- a/tests/debuginfo/reference-debuginfo.rs +++ b/tests/debuginfo/reference-debuginfo.rs @@ -2,10 +2,18 @@ // That pass replaces debuginfo for `a => _x` where `_x = &b` to be `a => &b`, // and leaves codegen to create a ladder of allocations so as `*a == b`. // +// FIXME: Currently emits warning: MIR pass `ConstDebugInfo` is unknown and will be ignored //@ compile-flags:-g -Zmir-enable-passes=+ReferencePropagation,-ConstDebugInfo //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + // === GDB TESTS =================================================================================== //@ gdb-command:run @@ -54,6 +62,8 @@ //@ gdb-command:print *f64_ref //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + //@ gdb-command:print *f64_double_ref //@ gdb-check:$16 = 3.5 @@ -106,11 +116,14 @@ //@ lldb-command:v *f64_ref //@ lldb-check:[...] 3.5 +//@ lldb-command:v *f128_ref +//@[not-apple] lldb-check:[...] 4.5 + //@ lldb-command:v *f64_double_ref //@ lldb-check:[...] 3.5 #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let bool_val: bool = true; @@ -159,6 +172,9 @@ fn main() { let f64_ref: &f64 = &f64_val; let f64_double_ref: &f64 = &f64_ref; + let f128_val: f128 = 4.5; + let f128_ref: &f128 = &f128_val; + zzz(); // #break } From 3d281d48815e6213ad9911e97d5d8d59d2348b1a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 3 Sep 2026 21:25:04 +0200 Subject: [PATCH 74/76] implement `VaArgSafe` for `f128` --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 3 +- library/core/src/ffi/va_list.rs | 49 +++++++++++++++++ .../c-link-to-rust-va-list-fn/checkrust.rs | 55 ++++++++++++++++++- .../run-make/c-link-to-rust-va-list-fn/test.c | 39 +++++++++++++ tests/ui/c-variadic/roundtrip.rs | 36 +++++++++++- 5 files changed, 177 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index db896fa9c0f2b..c71e83d8f99bd 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -347,8 +347,7 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { // 64-bit floats are always OK. } Primitive::Float(Float::F128) => { - // FIXME(f128) figure out whether we should support this. - bug!("the va_arg intrinsic does not support `f128`") + // Supported on some targets, especially where long double is IEEE f128. } } diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 21f8e264db953..b308cb7f85df2 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -416,6 +416,55 @@ cfg_select! { #[unstable(feature = "c_variadic_va_arg_safe", issue = "162911", implied_by = "c_variadic")] unsafe impl VaArgSafe for f64 {} +// Implement `VaArgSafe` for f128 on targets where either: +// +// - clang provides `__float128` +// - `long double` is IEEE f128 on the platform. +// +// When updating this cfg, also update the tests to match. Currently this condition +// is duplicated in: +// +// - tests/ui/c-variadic/roundtrip.rs +// - tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +// +// # Known incompatibilities +// +// Testing versus clang exposed bugs in clang. GCC has no known incompatibilities. +// +// - Clang <= 23 on sparc, see https://github.com/llvm/llvm-project/pull/214981. +// - Clang <= 23 on x86, see https://github.com/llvm/llvm-project/issues/217747. +cfg_select! { + any( + all(target_arch = "x86_64", not(target_vendor = "apple"), not(target_env = "msvc")), + all(target_arch = "x86", not(target_vendor = "apple"), not(target_env = "msvc")), + // PowerPC requires VSX (only little endian has it enabled by default). + all(target_arch = "powerpc64", target_feature = "vsx"), + all( + not(windows), + not(target_vendor = "apple"), + any( + target_arch = "aarch64", + target_arch = "loongarch32", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "wasm32", + target_arch = "wasm64", + ), + ), + ) => { + #[unstable_feature_bound(f128)] + #[unstable(feature = "f128", issue = "116909")] + unsafe impl VaArgSafe for f128 {} + } + _ => { /* unsupported */ } +} + #[unstable(feature = "c_variadic_va_arg_safe", issue = "162911", implied_by = "c_variadic")] unsafe impl VaArgSafe for *mut T {} #[unstable(feature = "c_variadic_va_arg_safe", issue = "162911", implied_by = "c_variadic")] diff --git a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs index 37110e75f4779..3f9698afe2d6c 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +++ b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs @@ -1,5 +1,5 @@ #![crate_type = "staticlib"] -#![feature(c_variadic_int128, c_variadic_experimental_arch)] +#![feature(c_variadic_int128, c_variadic_experimental_arch, f128)] use core::ffi::{CStr, VaList, c_char, c_double, c_int, c_long, c_longlong}; @@ -100,6 +100,59 @@ pub unsafe extern "C" fn check_list_i128(mut ap: VaList) -> usize { } } +cfg_select! { + any( + all(target_arch = "x86_64", not(target_vendor = "apple"), not(target_env = "msvc")), + all(target_arch = "x86", not(target_vendor = "apple"), not(target_env = "msvc")), + all(target_arch = "powerpc64", target_feature = "vsx"), + all( + not(windows), + not(target_vendor = "apple"), + any( + target_arch = "aarch64", + target_arch = "loongarch32", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "wasm32", + target_arch = "wasm64", + ), + ), + ) => { + #[unsafe(no_mangle)] + pub static RUST_HAS_F128: c_int = 1; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn check_list_f128(mut ap: VaList) -> usize { + continue_if!(ap.next_arg::() == -42.0); + // use a 32-bit value here to test the alignment logic. + continue_if!(ap.next_arg::() == 0xAAAA_AAAAu32.cast_signed()); + continue_if!(ap.next_arg::() == f128::MAX); + + return 0; + } + } + _ => { + #[unsafe(no_mangle)] + pub static RUST_HAS_F128: c_int = 0; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn check_list_f128(_: VaList) -> usize { + // This function was called a platform where rustc does not implement + // VaArgSafe for f128 but clang does define _Float128. + // + // This occurs on powerpc64 where f128 support depends on a target feature. + // + // Otherwise, rustc should add the implementation if this comes up. + 0xFF + } + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize { continue_if!(ap.next_arg::() == 42); diff --git a/tests/run-make/c-link-to-rust-va-list-fn/test.c b/tests/run-make/c-link-to-rust-va-list-fn/test.c index c7510a29445a5..14c507cd6cccd 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/test.c +++ b/tests/run-make/c-link-to-rust-va-list-fn/test.c @@ -3,12 +3,14 @@ #include #include #include +#include extern size_t check_list_0(va_list ap); extern size_t check_list_1(va_list ap); extern size_t check_list_2(va_list ap); extern size_t check_list_copy_0(va_list ap); extern size_t check_list_i128(va_list ap); +extern size_t check_list_f128(va_list ap); extern size_t check_varargs_0(int fixed, ...); extern size_t check_varargs_1(int fixed, ...); extern size_t check_varargs_2(int fixed, ...); @@ -21,6 +23,9 @@ extern size_t run_test_va_list_by_value(); extern size_t run_test_va_list_by_pointer(); extern size_t run_test_va_list_by_pointer_pointer(); +// Was the rust side compiled with f128 support? +extern const int RUST_HAS_F128; + int test_rust(size_t (*fn)(va_list), ...) { size_t ret = 0; va_list ap; @@ -40,9 +45,43 @@ int main(int argc, char* argv[]) { assert(test_rust(check_list_copy_0, 6.28, 16, 'A', "Skip Me!", "Correct") == 0); #if defined(__SIZEOF_INT128__) + assert(test_rust(check_list_i128, (__int128)-42, 0xAAAAAAAA, (unsigned __int128)-1) == 0); #endif + // Run the f128 test when __float128/_Float128 is defined or long double is IEEE f128. + // Use #define instead of typedef so that `#ifdef` can detect it. +#if defined(__LDBL_MANT_DIG__) && __LDBL_MANT_DIG__ == 113 +#define f128 long double +#elif defined(__SIZEOF_FLOAT128__) +#ifdef __clang__ +#define f128 __float128 +#else +#define f128 _Float128 +#endif +#endif + +#ifdef f128 + // construct f128::MAX. + union cvt128 { + struct { +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + uint64_t hi, lo; +#else + uint64_t lo, hi; +#endif + } i; + f128 f; + }; + union cvt128 f128_max; + f128_max.i.hi = 0x7ffeffffffffffff; + f128_max.i.lo = 0xffffffffffffffff; + + if (RUST_HAS_F128) { + assert(test_rust(check_list_f128, (f128)-42.0, 0xAAAAAAAA, f128_max.f) == 0); + } +#endif + assert(check_varargs_0(0, 42, "Hello, World!") == 0); assert(check_varargs_1(0, 3.14, 12l, 'A', 0x1LL) == 0); diff --git a/tests/ui/c-variadic/roundtrip.rs b/tests/ui/c-variadic/roundtrip.rs index 9fa55c36e2441..1fb96ab7cfad7 100644 --- a/tests/ui/c-variadic/roundtrip.rs +++ b/tests/ui/c-variadic/roundtrip.rs @@ -5,7 +5,8 @@ c_variadic_va_arg_safe, c_variadic_int128, const_destruct, - const_raw_ptr_comparison + const_raw_ptr_comparison, + f128 )] #![allow(unused_features)] // c_variadic_int128 is only used on 64-bit targets. @@ -113,7 +114,38 @@ fn main() { roundtrip!(i128, -1, -2); roundtrip!(u128, 1, 2); } - _ => {} + _ => { /* unsupported */ } + } + + cfg_select! { + any( + all( + any(target_arch = "x86_64", target_arch = "x86"), + not(target_vendor = "apple"), + not(target_env = "msvc") + ), + all(target_arch = "powerpc64", target_feature = "vsx"), + all( + not(windows), + not(target_vendor = "apple"), + any( + target_arch = "aarch64", + target_arch = "loongarch32", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "wasm32", + target_arch = "wasm64", + ), + ), + ) => { + roundtrip!(f128, -1.0, f128::MAX); + } + _ => { /* unsupported */ } } } } From a3ad87e548a932b761f387641e23dc8a24159fc7 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 18 Sep 2026 10:34:57 +0200 Subject: [PATCH 75/76] mark `f128` as reliable on `powerpc` with `+vsx` This feature is only enabled by default for powerpc64le, and not enabled on the big-endian targets. Even with vsx, the default long double is ppcf128 on the big-endian targets, so LLVM calls the incorrect libcall. Also some libcalls just don't exist --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 90f31e0598f2d..5324ec240b0ab 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -6,6 +6,7 @@ use std::sync::Once; use std::{ptr, slice, str}; use libc::c_int; +use rustc_abi::Endian; use rustc_codegen_ssa::back::versioned_llvm_target; use rustc_codegen_ssa::base::wants_wasm_eh; use rustc_codegen_ssa::target_features::internal_target_features; @@ -15,7 +16,7 @@ use rustc_data_structures::small_c_str::SmallCStr; use rustc_fs_util::path_to_c_string; use rustc_session::config::{NATIVE_CPU, PrintKind, PrintRequest}; use rustc_session::{EarlySession, Session}; -use rustc_span::bug; +use rustc_span::{bug, sym}; use rustc_target::spec::{ Arch, CfgAbi, Env, MergeFunctions, Os, PanicStrategy, SmallDataThresholdSupport, Target, }; @@ -394,6 +395,7 @@ pub(crate) fn target_config(sess: &EarlySession) -> TargetConfig { fn update_target_reliable_float_cfg(target: &Target, cfg: &mut TargetConfig) { let target_arch = &target.arch; let target_os = &target.options.os; + let target_endian = &target.options.endian; let target_env = &target.options.env; let target_abi = &target.options.cfg_abi; let target_pointer_width = target.pointer_width; @@ -426,9 +428,12 @@ fn update_target_reliable_float_cfg(target: &Target, cfg: &mut TargetConfig) { // Selection bug . This issue is closed // but basic math still does not work. (Arch::Nvptx64, _) => false, - // ABI bugs et al. (full - // list at ) - (Arch::PowerPC | Arch::PowerPC64, _) => false, + // ABI/LLVM bugs: + // - with +vsx + // - without +vsx + (Arch::PowerPC, _) => false, + // ABI bugs on BE without +vsx . + (Arch::PowerPC64, _) => cfg.internal_target_features.contains(&sym::vsx), // ABI unsupported (fixed in llvm22) (Arch::Sparc, _) if major < 22 => false, // MinGW ABI bugs (fixed in llvm23) @@ -456,9 +461,13 @@ fn update_target_reliable_float_cfg(target: &Target, cfg: &mut TargetConfig) { // (ld is `f64`), anything other than Linux (Windows and MacOS use `f64`), and `x86` // (ld is 80-bit extended precision). // + // On big-endian powerpc the symbol selection is correct, despite __ibmf128 being + // long double on the target, but the f128 symbols are not defined. + // // musl does not implement the symbols required for f128 math at all. _ if *target_env == Env::Musl => false, (Arch::X86_64, _) => false, + (Arch::PowerPC | Arch::PowerPC64, _) if *target_endian == Endian::Big => false, (_, Os::Linux) if target_pointer_width == 64 => true, _ => false, } && cfg.has_reliable_f128; From b2c6d2d7b45f0b0f1894d05d6590a8b0250c8303 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Fri, 28 Aug 2026 14:39:30 +0000 Subject: [PATCH 76/76] Refactor LivenessResults into LivenessComputation, without typeck --- .../rustc_borrowck/src/region_infer/values.rs | 10 - .../src/type_check/liveness/mod.rs | 20 +- .../src/type_check/liveness/trace.rs | 467 ++++++++++-------- 3 files changed, 272 insertions(+), 225 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 35009c3bad485..2f03fec0d2245 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -409,16 +409,6 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { } } -/// For debugging purposes, returns a pretty-printed string of the given points. -pub(crate) fn pretty_print_points( - location_map: &DenseLocationMap, - points: impl IntoIterator, -) -> String { - pretty_print_region_elements( - points.into_iter().map(|p| location_map.to_location(p)).map(RegionElement::Location), - ) -} - /// For debugging purposes, returns a pretty-printed string of the given region elements. fn pretty_print_region_elements<'tcx>( elements: impl IntoIterator>, diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs index 2de6635e93ac7..c2fa8ab8637af 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs @@ -1,15 +1,18 @@ use itertools::{Either, Itertools}; use rustc_data_structures::fx::FxHashSet; +use rustc_index::interval::IntervalSet; use rustc_middle::mir::visit::{TyContext, Visitor}; use rustc_middle::mir::{Body, Local, Location, SourceInfo}; use rustc_middle::ty::relate::Relate; use rustc_middle::ty::{GenericArgsRef, Region, RegionVid, Ty, TyCtxt, TypeVisitable}; use rustc_mir_dataflow::move_paths::MoveData; -use rustc_mir_dataflow::points::DenseLocationMap; +use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; use rustc_span::span_bug; +use rustc_trait_selection::traits::outlives_for_liveness::FreeRegionsVisitor; use tracing::debug; use super::TypeChecker; +use crate::BorrowckInferCtxt; use crate::constraints::OutlivesConstraintSet; use crate::polonius::{PoloniusContext, record_live_region_variance}; use crate::region_infer::values::LivenessValues; @@ -229,3 +232,18 @@ impl<'a, 'tcx> LiveVariablesVisitor<'a, 'tcx> { } } } + +pub(crate) fn make_all_regions_live<'tcx>( + infcx: &BorrowckInferCtxt<'tcx>, + universal_regions: &UniversalRegions<'tcx>, + liveness: &mut LivenessValues, + value: impl TypeVisitable>, + live_at: &IntervalSet, +) { + debug!("make_all_regions_live(value={value:?})"); + value.visit_with(&mut FreeRegionsVisitor { + tcx: infcx.tcx, + param_env: infcx.param_env, + op: |r| liveness.add_points(universal_regions.to_region_vid(r), live_at), + }); +} diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 90126866cd500..d0302faa513b2 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -6,7 +6,7 @@ use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_infer::traits::TraitErrors; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; -use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; +use rustc_middle::ty::{GenericArg, Ty, TypeVisitableExt}; use rustc_mir_dataflow::impls::MaybeInitializedPlaces; use rustc_mir_dataflow::move_paths::{HasMoveData, MoveData, MovePathIndex}; use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; @@ -14,16 +14,17 @@ use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::ObligationCtxt; -use rustc_trait_selection::traits::outlives_for_liveness::FreeRegionsVisitor; use rustc_trait_selection::traits::query::dropck_outlives; use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOpOutput}; use tracing::debug; -use crate::BorrowckInferCtxt; -use crate::polonius::{self, record_live_region_variance}; -use crate::region_infer::values; +use crate::polonius::{LiveRegionVariances, record_live_region_variance}; +use crate::region_infer::values::LivenessValues; use crate::type_check::liveness::local_use_map::LocalUseMap; +use crate::type_check::liveness::make_all_regions_live; use crate::type_check::{NormalizeLocation, TypeChecker}; +use crate::universal_regions::UniversalRegions; +use crate::{BorrowckInferCtxt, polonius}; /// This is the heart of the liveness computation. For each variable X /// that requires a liveness computation, it walks over all the uses @@ -48,33 +49,28 @@ pub(super) fn trace<'tcx>( ) { let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness_trace"); - let local_use_map = &LocalUseMap::build(&relevant_live_locals, location_map, typeck.body); - let cx = LivenessContext { - typeck, - flow_inits: None, + let local_use_map = LocalUseMap::build(&relevant_live_locals, location_map, typeck.body); + let comp = LivenessComputation::new( + typeck.infcx, + typeck.body, location_map, - local_use_map, move_data, - term_states: IndexVec::new(), - exit_states: IndexVec::new(), - drop_data: FxIndexMap::default(), - }; + &local_use_map, + ); - let mut results = LivenessResults::new(cx); + let mut results = LivenessResults::new(typeck, comp); - results.add_extra_drop_facts(relevant_live_locals); + results.record_legacy_polonius_drop_facts(relevant_live_locals); results.compute_for_all_locals(relevant_live_locals); results.dropck_boring_locals(boring_locals); } -/// Contextual state for the type-liveness coroutine. -struct LivenessContext<'a, 'typeck, 'tcx> { - /// Current type-checker, giving us our inference context etc. - /// - /// This also stores the body we're currently analyzing. - typeck: &'a mut TypeChecker<'typeck, 'tcx>, +pub(crate) struct LivenessComputation<'a, 'tcx> { + pub(crate) infcx: &'a BorrowckInferCtxt<'tcx>, + + pub(crate) body: &'a Body<'tcx>, /// Defines the `PointIndex` mapping location_map: &'a DenseLocationMap, @@ -82,9 +78,6 @@ struct LivenessContext<'a, 'typeck, 'tcx> { /// Mapping to/from the various indices used for initialization tracking. move_data: &'a MoveData<'tcx>, - /// Cache for the results of `dropck_outlives` query. - drop_data: FxIndexMap, DropData<'tcx>>, - /// Results of dataflow tracking which variables (and paths) have been /// initialized. Computed lazily when needed by drop-liveness. flow_inits: Option>>, @@ -96,15 +89,6 @@ struct LivenessContext<'a, 'typeck, 'tcx> { // Caches for the results of `initialized_at_terminator` and `initialized_at_exit`. term_states: IndexVec>>>, exit_states: IndexVec>>>, -} - -struct DropData<'tcx> { - dropck_result: DropckOutlivesResult<'tcx>, - region_constraint_data: Option<&'tcx QueryRegionConstraints<'tcx>>, -} - -struct LivenessResults<'a, 'typeck, 'tcx> { - cx: LivenessContext<'a, 'typeck, 'tcx>, /// Set of points that define the current local. defs: DenseBitSet, @@ -125,43 +109,70 @@ struct LivenessResults<'a, 'typeck, 'tcx> { stack: Vec, } +struct LivenessResults<'a, 'typeck, 'tcx> { + /// Current type-checker, giving us our inference context etc. + typeck: &'a mut TypeChecker<'typeck, 'tcx>, + + /// Cache for the results of `dropck_outlives` query. + drop_data: FxIndexMap, DropData<'tcx>>, + + comp: LivenessComputation<'a, 'tcx>, +} + impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { - fn new(cx: LivenessContext<'a, 'typeck, 'tcx>) -> Self { - let num_points = cx.location_map.num_points(); - LivenessResults { - cx, - defs: DenseBitSet::new_empty(num_points), - use_live_at: IntervalSet::new(num_points), - drop_live_at: DenseBitSet::new_empty(num_points), - drop_locations: vec![], - stack: vec![], - } + fn new( + typeck: &'a mut TypeChecker<'typeck, 'tcx>, + comp: LivenessComputation<'a, 'tcx>, + ) -> Self { + LivenessResults { typeck, drop_data: FxIndexMap::default(), comp } } fn compute_for_all_locals(&mut self, relevant_live_locals: &[Local]) { for &local in relevant_live_locals { - self.reset_local_state(); - self.add_defs_for(local); - self.compute_use_live_points_for(local); - self.compute_drop_live_points_for(local); + self.compute_for_local(local); + } + } - let local_ty = self.cx.body().local_decls[local].ty; + fn compute_for_local(&mut self, local: Local) { + // If we end up needing to compute the drop data (because there are + // drop-live points), then we need to register region constraints and + // emit drop facts. + let mut computed_drop_data = None; + + self.comp.compute( + local, + self.typeck.universal_regions, + self.typeck.polonius_context.as_mut().map(|c| &mut c.live_region_variances), + &mut self.typeck.constraints.liveness_constraints, + || { + let local_ty = self.comp.body.local_decls[local].ty; + let local_span = self.comp.body.local_decls[local].source_info.span; + let drop_data = + dropck_local(&self.typeck.infcx, &mut self.drop_data, local_ty, local_span); + let drop_data = computed_drop_data.insert(drop_data); + &drop_data.dropck_result.kinds + }, + ); - if !self.use_live_at.is_empty() { - self.cx.add_use_live_facts_for(local_ty, &self.use_live_at); + if let Some(drop_data) = computed_drop_data { + if let Some(data) = &drop_data.region_constraint_data { + for &drop_location in &self.comp.drop_locations { + self.typeck.push_region_constraints( + drop_location.to_locations(), + ConstraintCategory::Boring, + data, + ); + } } - if !self.drop_live_at.is_empty() { - // `drop_live_at` is using a DenseBitSet, but `add_drop_live_facts_for` expects - // an IntervalSet. We thus convert between those two here. - let mut set: IntervalSet = - IntervalSet::new(self.drop_live_at.domain_size()); - for item in self.drop_live_at.iter() { - // We iterate the `drop_live_at` set from smallest to largest values, so - // we can use append to add things to the interval set at the end. - set.append(item); - } - self.cx.add_drop_live_facts_for(local, local_ty, &self.drop_locations, &set); + for &kind in &drop_data.dropck_result.kinds { + polonius::legacy::emit_drop_facts( + self.typeck.tcx(), + local, + &kind, + self.typeck.universal_regions, + self.typeck.polonius_facts, + ); } } } @@ -174,27 +185,26 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { /// and can therefore safely be dropped. fn dropck_boring_locals(&mut self, boring_locals: &[Local]) { for &local in boring_locals { - let local_ty = self.cx.body().local_decls[local].ty; - let local_span = self.cx.body().local_decls[local].source_info.span; - dropck_local(&self.cx.typeck.infcx, &mut self.cx.drop_data, local_ty, local_span); + let local_ty = self.comp.body.local_decls[local].ty; + let local_span = self.comp.body.local_decls[local].source_info.span; + dropck_local(&self.typeck.infcx, &mut self.drop_data, local_ty, local_span); } } - /// Add extra drop facts needed for Polonius. + /// Add extra drop facts needed for Polonius Legacy. /// /// Add facts for all locals with free regions, since regions may outlive /// the function body only at certain nodes in the CFG. - fn add_extra_drop_facts(&mut self, relevant_live_locals: &[Local]) { - // This collect is more necessary than immediately apparent - // because these facts go into `add_drop_live_facts_for()`, - // which also writes to `polonius_facts`, and so this is genuinely - // a simultaneous overlapping mutable borrow. + fn record_legacy_polonius_drop_facts(&mut self, relevant_live_locals: &[Local]) { + // This is *all wonky* because this used to call a shared + // `add_drop_live_facts_for` function that was also used for regular + // relevant locals. Presumably, this can be cleaned up quite a bit. // FIXME for future hackers: investigate whether this is // actually necessary; these facts come from Polonius // and probably maybe plausibly does not need to go back in. // It may be necessary to just pick out the parts of // `add_drop_live_facts_for()` that make sense. - let Some(facts) = self.cx.typeck.polonius_facts.as_ref() else { return }; + let Some(facts) = self.typeck.polonius_facts.as_ref() else { return }; let facts_to_add: Vec<_> = { let relevant_live_locals: FxIndexSet<_> = relevant_live_locals.iter().copied().collect(); @@ -203,20 +213,155 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { .var_dropped_at .iter() .filter_map(|&(local, location_index)| { - let local_ty = self.cx.body().local_decls[local].ty; + let local_ty = self.comp.body.local_decls[local].ty; if relevant_live_locals.contains(&local) || !local_ty.has_free_regions() { return None; } - let location = self.cx.typeck.location_table.to_location(location_index); + let location = self.typeck.location_table.to_location(location_index); Some((local, local_ty, location)) }) .collect() }; - let live_at = IntervalSet::new(self.cx.location_map.num_points()); + let live_at = IntervalSet::new(self.comp.location_map.num_points()); for (local, local_ty, location) in facts_to_add { - self.cx.add_drop_live_facts_for(local, local_ty, &[location], &live_at); + let local_span = self.comp.body.local_decls[local].source_info.span; + let drop_data = + dropck_local(&self.typeck.infcx, &mut self.drop_data, local_ty, local_span); + + if let Some(data) = &drop_data.region_constraint_data { + self.typeck.push_region_constraints( + location.to_locations(), + ConstraintCategory::Boring, + data, + ); + } + + for &kind in &drop_data.dropck_result.kinds { + make_all_regions_live( + self.typeck.infcx, + self.typeck.universal_regions, + &mut self.typeck.constraints.liveness_constraints, + kind, + &live_at, + ); + polonius::legacy::emit_drop_facts( + self.typeck.tcx(), + local, + &kind, + self.typeck.universal_regions, + self.typeck.polonius_facts, + ); + } + + if let Some(polonius_context) = self.typeck.polonius_context.as_mut() { + record_live_region_variance( + self.typeck.infcx.tcx, + &mut polonius_context.live_region_variances, + self.typeck.universal_regions, + local_ty, + ); + } + } + } +} + +enum InitAtLocation { + Terminator, + Exit, +} + +impl<'a, 'tcx> LivenessComputation<'a, 'tcx> { + pub(crate) fn new( + infcx: &'a BorrowckInferCtxt<'tcx>, + body: &'a Body<'tcx>, + location_map: &'a DenseLocationMap, + move_data: &'a MoveData<'tcx>, + local_use_map: &'a LocalUseMap, + ) -> Self { + let num_points = location_map.num_points(); + LivenessComputation { + infcx, + body, + location_map, + move_data, + flow_inits: None, + local_use_map, + term_states: IndexVec::new(), + exit_states: IndexVec::new(), + defs: DenseBitSet::new_empty(num_points), + use_live_at: IntervalSet::new(num_points), + drop_live_at: DenseBitSet::new_empty(num_points), + drop_locations: vec![], + stack: vec![], + } + } + + /// Compute for a given local the use- and drop-live points + fn compute<'drop_data>( + &mut self, + local: Local, + universal_regions: &UniversalRegions<'tcx>, + live_region_variances: Option<&mut LiveRegionVariances>, + liveness_constraints: &mut LivenessValues, + get_drop_args: impl FnOnce() -> &'drop_data Vec>, + ) where + 'tcx: 'drop_data, + { + self.reset_local_state(); + self.add_defs_for(local); + self.compute_use_live_points_for(local); + self.compute_drop_live_points_for(local); + + let local_ty = self.body.local_decls[local].ty; + + // When using `-Zpolonius=next`, we also record the variance of regions in this live type. + // For dropck in particular, note that we walk the type and not its live components seen in + // the dropck results. See issue #160670. + let is_live_anywhere = !self.use_live_at.is_empty() || !self.drop_live_at.is_empty(); + if is_live_anywhere && let Some(live_region_variances) = live_region_variances { + record_live_region_variance( + self.infcx.tcx, + live_region_variances, + universal_regions, + local_ty, + ); + } + if !self.use_live_at.is_empty() { + make_all_regions_live( + self.infcx, + universal_regions, + liveness_constraints, + local_ty, + &self.use_live_at, + ); + } + if !self.drop_live_at.is_empty() { + let drop_data = get_drop_args(); + + // `drop_live_at` is using a DenseBitSet, but `make_all_regions_live` + // expects an IntervalSet. We thus convert between those two here. + // Using a `DenseBitSet` has better performance, but storing liveness + // as a dense matrix has worse performance. There's probably room here + // for some cleanup, but this works for now. + let mut drop_live_at: IntervalSet = + IntervalSet::new(self.drop_live_at.domain_size()); + for item in self.drop_live_at.iter() { + // We iterate the `drop_live_at` set from smallest to largest values, so + // we can use append to add things to the interval set at the end. + drop_live_at.append(item); + } + + for &kind in drop_data { + make_all_regions_live( + self.infcx, + universal_regions, + liveness_constraints, + kind, + &drop_live_at, + ); + } } } @@ -231,7 +376,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { /// Adds the definitions of `local` into `self.defs`. fn add_defs_for(&mut self, local: Local) { - for def in self.cx.local_use_map.defs(local) { + for def in self.local_use_map.defs(local) { debug!("- defined at {:?}", def); self.defs.insert(def); } @@ -246,14 +391,14 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn compute_use_live_points_for(&mut self, local: Local) { debug!("compute_use_live_points_for(local={:?})", local); - self.stack.extend(self.cx.local_use_map.uses(local)); + self.stack.extend(self.local_use_map.uses(local)); while let Some(p) = self.stack.pop() { // We are live in this block from the closest to us of: // // * Inclusively, the block start // * Exclusively, the previous definition (if it's in this block) // * Exclusively, the previous live_at setting (an optimization) - let block_start = self.cx.location_map.to_block_start(p); + let block_start = self.location_map.to_block_start(p); let previous_defs = self.defs.last_set_in(block_start..=p); let previous_live_at = self.use_live_at.last_set_in(block_start..=p); @@ -277,12 +422,12 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // terminators of predecessor basic blocks. Push those onto the // stack so that the next iteration(s) will process them. - let block = self.cx.location_map.to_location(block_start).block; + let block = self.location_map.to_location(block_start).block; self.stack.extend( - self.cx.body().basic_blocks.predecessors()[block] + self.body.basic_blocks.predecessors()[block] .iter() - .map(|&pred_bb| self.cx.body().terminator_loc(pred_bb)) - .map(|pred_loc| self.cx.location_map.point_from_location(pred_loc)), + .map(|&pred_bb| self.body.terminator_loc(pred_bb)) + .map(|pred_loc| self.location_map.point_from_location(pred_loc)), ); } } @@ -300,15 +445,15 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn compute_drop_live_points_for(&mut self, local: Local) { debug!("compute_drop_live_points_for(local={:?})", local); - let Some(mpi) = self.cx.move_data.rev_lookup.find_local(local) else { return }; + let Some(mpi) = self.move_data.rev_lookup.find_local(local) else { return }; debug!("compute_drop_live_points_for: mpi = {:?}", mpi); // Find the drops where `local` is initialized. - for drop_point in self.cx.local_use_map.drops(local) { - let location = self.cx.location_map.to_location(drop_point); - debug_assert_eq!(self.cx.body().terminator_loc(location.block), location,); + for drop_point in self.local_use_map.drops(local) { + let location = self.location_map.to_location(drop_point); + debug_assert_eq!(self.body.terminator_loc(location.block), location,); - if self.cx.initialized_at_terminator(location.block, mpi) { + if self.initialized_at_terminator(location.block, mpi) { let inserted = self.drop_live_at.insert(drop_point); // Right now, we should not visit a drop_point twice. // If we do, this will trigger a debug assert so we know we can optimize. @@ -342,8 +487,8 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn compute_drop_live_points_for_block(&mut self, mpi: MovePathIndex, term_point: PointIndex) { debug!( "compute_drop_live_points_for_block(mpi={:?}, term_point={:?})", - self.cx.move_data.move_paths[mpi].place, - self.cx.location_map.to_location(term_point), + self.move_data.move_paths[mpi].place, + self.location_map.to_location(term_point), ); // We are only invoked with terminators where `mpi` is @@ -353,14 +498,14 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // Otherwise, scan backwards through the statements in the // block. One of them may be either a definition or use // live point. - let term_location = self.cx.location_map.to_location(term_point); - debug_assert_eq!(self.cx.body().terminator_loc(term_location.block), term_location,); + let term_location = self.location_map.to_location(term_point); + debug_assert_eq!(self.body.terminator_loc(term_location.block), term_location,); let block = term_location.block; - let entry_point = self.cx.location_map.entry_point(term_location.block); + let entry_point = self.location_map.entry_point(term_location.block); for p in (entry_point..term_point).rev() { debug!( "compute_drop_live_points_for_block: p = {:?}", - self.cx.location_map.to_location(p) + self.location_map.to_location(p) ); if self.defs.contains(p) { @@ -379,7 +524,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { } } - let body = self.cx.typeck.body; + let body = self.body; for &pred_block in body.basic_blocks.predecessors()[block].iter() { debug!("compute_drop_live_points_for_block: pred_block = {:?}", pred_block,); @@ -401,13 +546,13 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // terminator. *But*, in that case, the terminator is also // a *definition* of the variable, in which case we want // to stop the search anyhow. (But see Note 1 below.) - if !self.cx.initialized_at_exit(pred_block, mpi) { + if !self.initialized_at_exit(pred_block, mpi) { debug!("compute_drop_live_points_for_block: not initialized"); continue; } - let pred_term_loc = self.cx.body().terminator_loc(pred_block); - let pred_term_point = self.cx.location_map.point_from_location(pred_term_loc); + let pred_term_loc = self.body.terminator_loc(pred_block); + let pred_term_point = self.location_map.point_from_location(pred_term_loc); // If the terminator of this predecessor either *assigns* // our value or is a "normal use", then stop. @@ -463,17 +608,6 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // for the call (`TMP = call()...`) and then a // `Drop(X)` followed by `X = TMP` to swap that with `X`. } -} - -enum InitAtLocation { - Terminator, - Exit, -} - -impl<'tcx> LivenessContext<'_, '_, 'tcx> { - fn body(&self) -> &Body<'tcx> { - self.typeck.body - } /// Returns `true` if the local variable (or some part of it) is initialized /// at the location defined by `init_at_location`. @@ -490,8 +624,8 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { // - there are relevant live locals // - there are drop points for these relevant live locals. let flow_inits = self.flow_inits.get_or_insert_with(|| { - let tcx = self.typeck.tcx(); - let body = self.typeck.body; + let tcx = self.infcx.tcx; + let body = self.body; // FIXME: reduce the `MaybeInitializedPlaces` domain to the useful `MovePath`s. // // This dataflow analysis computes maybe-initializedness of all move paths, which @@ -515,7 +649,7 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { InitAtLocation::Exit => &mut self.exit_states, }; let state = states.get_or_insert_with(block, || { - let terminator_location = self.typeck.body.terminator_loc(block); + let terminator_location = self.body.terminator_loc(block); match init_at_location { InitAtLocation::Terminator => { flow_inits.seek_before_primary_effect(terminator_location) @@ -548,109 +682,14 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { fn initialized_at_exit(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool { self.initialized_at(block, mpi, InitAtLocation::Exit) } +} - /// Stores the result that all regions in `value` are live for the - /// points `live_at`. - fn add_use_live_facts_for(&mut self, value: Ty<'tcx>, live_at: &IntervalSet) { - debug!("add_use_live_facts_for(value={:?})", value); - Self::make_all_regions_live(self.location_map, self.typeck, value, live_at); - - // When using `-Zpolonius=next`, we also record the variance of regions in this live type. - if let Some(polonius_context) = self.typeck.polonius_context.as_mut() { - record_live_region_variance( - self.typeck.infcx.tcx, - &mut polonius_context.live_region_variances, - self.typeck.universal_regions, - value, - ); - } - } - - /// Some variable with type `live_ty` is "drop live" at `location` - /// -- i.e., it may be dropped later. This means that *some* of - /// the regions in its type must be live at `location`. The - /// precise set will depend on the dropck constraints, and in - /// particular this takes `#[may_dangle]` into account. - fn add_drop_live_facts_for( - &mut self, - dropped_local: Local, - dropped_ty: Ty<'tcx>, - drop_locations: &[Location], - live_at: &IntervalSet, - ) { - debug!( - "add_drop_live_constraint(\ - dropped_local={:?}, \ - dropped_ty={:?}, \ - drop_locations={:?}, \ - live_at={:?})", - dropped_local, - dropped_ty, - drop_locations, - values::pretty_print_points(self.location_map, live_at.iter()), - ); - - let dropped_span = self.body().local_decls[dropped_local].source_info.span; - let drop_data = - dropck_local(&self.typeck.infcx, &mut self.drop_data, dropped_ty, dropped_span); - - if let Some(data) = &drop_data.region_constraint_data { - for &drop_location in drop_locations { - self.typeck.push_region_constraints( - drop_location.to_locations(), - ConstraintCategory::Boring, - data, - ); - } - } - - // All things in the `outlives` array may be touched by - // the destructor and must be live at this point. - for &kind in &drop_data.dropck_result.kinds { - Self::make_all_regions_live(self.location_map, self.typeck, kind, live_at); - polonius::legacy::emit_drop_facts( - self.typeck.tcx(), - dropped_local, - &kind, - self.typeck.universal_regions, - self.typeck.polonius_facts, - ); - } - - // For polonius: since the local is drop live, record the variance of the regions in its - // type, not the ones in the type's live components seen in the dropck results above. See - // issue #160670. - if let Some(polonius_context) = self.typeck.polonius_context.as_mut() { - record_live_region_variance( - self.typeck.infcx.tcx, - &mut polonius_context.live_region_variances, - self.typeck.universal_regions, - dropped_ty, - ); - } - } - - fn make_all_regions_live( - location_map: &DenseLocationMap, - typeck: &mut TypeChecker<'_, 'tcx>, - value: impl TypeVisitable>, - live_at: &IntervalSet, - ) { - debug!("make_all_regions_live(value={:?})", value); - debug!( - "make_all_regions_live: live_at={}", - values::pretty_print_points(location_map, live_at.iter()), - ); - - value.visit_with(&mut FreeRegionsVisitor { - tcx: typeck.tcx(), - param_env: typeck.infcx.param_env, - op: |r| { - let live_region_vid = typeck.universal_regions.to_region_vid(r); - typeck.constraints.liveness_constraints.add_points(live_region_vid, live_at); - }, - }); - } +/// Contains the results of computing dropck for a local. Namely, this includes +/// the dropped types, and overflows found, and the region constraints that must +/// hold at drop. +struct DropData<'tcx> { + dropck_result: DropckOutlivesResult<'tcx>, + region_constraint_data: Option<&'tcx QueryRegionConstraints<'tcx>>, } /// Computes the `DropData` for a given type, caching the result.