From 9abec7c30b95eead8a17da88a3caceba605c4112 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Sun, 7 Jun 2026 22:10:48 +0000 Subject: [PATCH 01/78] Move some tests around for alias-liveness --- ...-hide-lifetime-for-swap.edition2015.stderr | 11 ++++ ...-hide-lifetime-for-swap.edition2024.stderr | 11 ++++ .../gat-hide-lifetime-for-swap.rs | 50 +++++++++++++++++++ ...me-in-type-arg-for-swap.edition2015.stderr | 11 ++++ ...me-in-type-arg-for-swap.edition2024.stderr | 11 ++++ .../gat-hide-lifetime-in-type-arg-for-swap.rs | 50 +++++++++++++++++++ .../gat-outlives-env.edition2015.stderr | 13 +++++ .../gat-outlives-env.edition2024.stderr | 13 +++++ .../gat-outlives-env.polonius_alpha.stderr | 13 +++++ .../alias-liveness/gat-outlives-env.rs | 47 +++++++++++++++++ .../gat-outlives-implied.edition2024.stderr | 13 +++++ ...gat-outlives-implied.polonius_alpha.stderr | 13 +++++ .../alias-liveness/gat-outlives-implied.rs | 25 ++++++++++ .../borrowck/alias-liveness/gat-outlives.rs | 23 +++++++++ ...dden-erased-unsoundness.edition2015.stderr | 0 ...dden-erased-unsoundness.edition2024.stderr | 0 .../rpit-hidden-erased-unsoundness.rs | 0 ...hide-lifetime-for-swap.edition2015.stderr} | 4 +- ...hide-lifetime-for-swap.edition2024.stderr} | 4 +- .../rpit-hide-lifetime-for-swap.rs} | 0 ...me-in-type-arg-for-swap.edition2015.stderr | 11 ++++ ...me-in-type-arg-for-swap.edition2024.stderr | 11 ++++ ...rpit-hide-lifetime-in-type-arg-for-swap.rs | 40 +++++++++++++++ ...-hide-lifetime-for-swap.edition2015.stderr | 11 ++++ ...-hide-lifetime-for-swap.edition2024.stderr | 11 ++++ .../rpitit-hide-lifetime-for-swap.rs | 40 +++++++++++++++ .../tait-hidden-erased-unsoundness-2.rs | 0 .../tait-hidden-erased-unsoundness-2.stderr | 0 .../tait-hidden-erased-unsoundness.rs | 0 .../tait-hidden-erased-unsoundness.stderr | 0 ...-hide-lifetime-for-swap.edition2015.stderr | 18 ------- ...-hide-lifetime-for-swap.edition2024.stderr | 17 ------- .../rpit-hide-lifetime-for-swap.rs | 42 ---------------- 33 files changed, 432 insertions(+), 81 deletions(-) create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-env.rs create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs create mode 100644 tests/ui/borrowck/alias-liveness/gat-outlives.rs rename tests/ui/{impl-trait => borrowck}/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr (100%) rename tests/ui/{impl-trait => borrowck}/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr (100%) rename tests/ui/{impl-trait => borrowck}/alias-liveness/rpit-hidden-erased-unsoundness.rs (100%) rename tests/ui/{impl-trait/hidden-lifetimes.edition2015.stderr => borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr} (94%) rename tests/ui/{impl-trait/hidden-lifetimes.edition2024.stderr => borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr} (91%) rename tests/ui/{impl-trait/hidden-lifetimes.rs => borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs} (100%) create mode 100644 tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr create mode 100644 tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr create mode 100644 tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs create mode 100644 tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr create mode 100644 tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr create mode 100644 tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs rename tests/ui/{impl-trait => borrowck}/alias-liveness/tait-hidden-erased-unsoundness-2.rs (100%) rename tests/ui/{impl-trait => borrowck}/alias-liveness/tait-hidden-erased-unsoundness-2.stderr (100%) rename tests/ui/{impl-trait => borrowck}/alias-liveness/tait-hidden-erased-unsoundness.rs (100%) rename tests/ui/{impl-trait => borrowck}/alias-liveness/tait-hidden-erased-unsoundness.stderr (100%) delete mode 100644 tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr delete mode 100644 tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr delete mode 100644 tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..59d7bf0b938d5 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-for-swap.rs:35:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..59d7bf0b938d5 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-for-swap.rs:35:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs new file mode 100644 index 0000000000000..bbb9f9aee6c45 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs @@ -0,0 +1,50 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next +//@ [polonius_alpha] known-bug: #153215 +//@ [polonius_alpha] check-pass + +// Test to show what happens if we were not careful and allowed invariant +// lifetimes to escape through a GAT. +// +// Specifically we swap a long lived and short lived reference, giving us a +// dangling pointer. + +trait Swap: Sized { + fn swap(self, other: Self); +} +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} +trait Hider { + type Hidden<'a, 'b, T: 'static>: Swap + 'a + where + Self: 'a, + 'b: 'a; + fn hide_ref<'a, 'b, T: 'static>(&self, x: &'a mut &'b T) -> Self::Hidden<'a, 'b, T>; +} +fn dangle_ref(h: &H) -> &'static [i32; 3] { + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` +} +struct H; +impl Hider for H { + type Hidden<'a, 'b, T: 'static> + = &'a mut &'b T + where + Self: 'a, + 'b: 'a; + fn hide_ref<'a, 'b, T: 'static>(&self, x: &'a mut &'b T) -> Self::Hidden<'a, 'b, T> { + x + } +} +fn main() { + println!("{:?}", dangle_ref(&H)); +} diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..e9703c4302af7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:35:5 + | +LL | h.hide(&mut res).swap(h.hide(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..e9703c4302af7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:35:5 + | +LL | h.hide(&mut res).swap(h.hide(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs new file mode 100644 index 0000000000000..04b62a460190f --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs @@ -0,0 +1,50 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next +//@ [polonius_alpha] known-bug: #153215 +//@ [polonius_alpha] check-pass + +// Like gat-hide-lifetime-for-swap, but the invariant lifetime is hidden +// inside a *type* argument of the GAT rather than being a region argument. +// The underlying type can capture `U` (and thus all of its regions) because +// of the declared `U: 'a` bound, so the regions inside the type argument +// must be considered live too. + +trait Swap: Sized { + fn swap(self, other: Self); +} +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} +trait Hider { + type Hidden<'a, U>: Swap + 'a + where + Self: 'a, + U: Swap + 'a; + fn hide<'a, U: Swap + 'a>(&'a self, x: U) -> Self::Hidden<'a, U>; +} +fn dangle(h: &H) -> &'static [i32; 3] { + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + h.hide(&mut res).swap(h.hide(&mut &x)); + res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` +} +struct H; +impl Hider for H { + type Hidden<'a, U> + = U + where + Self: 'a, + U: Swap + 'a; + fn hide<'a, U: Swap + 'a>(&'a self, x: U) -> Self::Hidden<'a, U> { + x + } +} +fn main() { + println!("{:?}", dangle(&H)); +} diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr new file mode 100644 index 0000000000000..66d530bc4ecad --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr @@ -0,0 +1,13 @@ +error[E0499]: cannot borrow `t` as mutable more than once at a time + --> $DIR/gat-outlives-env.rs:44:13 + | +LL | let a = t.changes(static_unit); + | - first mutable borrow occurs here +LL | let b = t.changes(static_unit); + | ^ second mutable borrow occurs here +LL | } + | - first borrow might be used here, when `a` is dropped and runs the destructor for type `::Changes<'_, '_>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr new file mode 100644 index 0000000000000..66d530bc4ecad --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr @@ -0,0 +1,13 @@ +error[E0499]: cannot borrow `t` as mutable more than once at a time + --> $DIR/gat-outlives-env.rs:44:13 + | +LL | let a = t.changes(static_unit); + | - first mutable borrow occurs here +LL | let b = t.changes(static_unit); + | ^ second mutable borrow occurs here +LL | } + | - first borrow might be used here, when `a` is dropped and runs the destructor for type `::Changes<'_, '_>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr new file mode 100644 index 0000000000000..66d530bc4ecad --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr @@ -0,0 +1,13 @@ +error[E0499]: cannot borrow `t` as mutable more than once at a time + --> $DIR/gat-outlives-env.rs:44:13 + | +LL | let a = t.changes(static_unit); + | - first mutable borrow occurs here +LL | let b = t.changes(static_unit); + | ^ second mutable borrow occurs here +LL | } + | - first borrow might be used here, when `a` is dropped and runs the destructor for type `::Changes<'_, '_>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs new file mode 100644 index 0000000000000..eaf79b06e8d1c --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs @@ -0,0 +1,47 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +trait Updater { + type Changes<'a, 'b: 'a> + where + Self: 'a; + fn changes<'a, 'b>(&'a mut self, _value: &'b u8) -> Self::Changes<'a, 'b>; +} + +fn overlapping_mut(mut t: T) +where + T: Updater, + for<'a, 'b> T::Changes<'a, 'b>: 'static, +{ + let static_unit: &'static u8 = &0; + let a = t.changes(static_unit); + let b = t.changes(static_unit); +} + +fn overlapping_mut2(mut t: T) +where + T: Updater, + for<'a, 'b> T::Changes<'a, 'b>: 'b, +{ + let static_unit: &'static u8 = &0; + let a = t.changes(static_unit); + let b = t.changes(static_unit); + // ^ These don't error, because the underlying type can't capture `&self`: + // it would have to outlive `'b` for arbitrary `'a` and `'b`. +} + +fn overlapping_mut3<'b, T>(mut t: T) +where + T: Updater, + for<'a> T::Changes<'a, 'b>: 'a, +{ + let static_unit: &'static u8 = &0; + let a = t.changes(static_unit); + let b = t.changes(static_unit); //[edition2015,edition2024,polonius_alpha]~ ERROR cannot borrow `t` as mutable more than once at a time +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr new file mode 100644 index 0000000000000..2d733d142f0f6 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr @@ -0,0 +1,13 @@ +error[E0506]: cannot assign to `cluster` because it is borrowed + --> $DIR/gat-outlives-implied.rs:20:9 + | +LL | let changes = self.changes(&cluster); + | -------- `cluster` is borrowed here +LL | cluster = 1; + | ^^^^^^^^^^^ `cluster` is assigned to here but it was already borrowed +LL | let _ = changes; + | ------- borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr new file mode 100644 index 0000000000000..2d733d142f0f6 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr @@ -0,0 +1,13 @@ +error[E0506]: cannot assign to `cluster` because it is borrowed + --> $DIR/gat-outlives-implied.rs:20:9 + | +LL | let changes = self.changes(&cluster); + | -------- `cluster` is borrowed here +LL | cluster = 1; + | ^^^^^^^^^^^ `cluster` is assigned to here but it was already borrowed +LL | let _ = changes; + | ------- borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs new file mode 100644 index 0000000000000..4248b0063179d --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs @@ -0,0 +1,25 @@ +//@ revisions: edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ edition: 2024 +//@ check-fail + +// Tests that liveness for regions in associated types considers outlives +// bounds, and the transitive implied outlives bounds from those. + + +trait Updater { + // Because `'b` is known to outlive `'a`, then we must consider that `'b` + // may be live in `Self::Changes<'a, 'b>`. + type Changes<'a, 'b: 'a>: 'a + where + Self: 'a; + fn changes<'a, 'b>(&'a self, _value: &'b u8) -> Self::Changes<'a, 'b>; + fn run(&self) { + let mut cluster = 0u8; + let changes = self.changes(&cluster); + cluster = 1; //[edition2024,polonius_alpha]~ ERROR cannot assign to `cluster` because it is borrowed + let _ = changes; + } +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives.rs b/tests/ui/borrowck/alias-liveness/gat-outlives.rs new file mode 100644 index 0000000000000..ce6beffb19043 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives.rs @@ -0,0 +1,23 @@ +//@ revisions: edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ check-pass + +// Tests that liveness for regions in associated types considers outlives bounds. + + +trait Updater { + // Because `'b` isn't known to outlive `'a`, then we know that + // `Self::Changes<'a, 'b>` should not need to consider `'b` to be live. + type Changes<'a, 'b>: 'a + where + Self: 'a; + fn changes<'a, 'b>(&'a self, _value: &'b u8) -> Self::Changes<'a, 'b>; + fn run(&self) { + let mut cluster = 0u8; + let changes = self.changes(&cluster); + cluster = 1; + let _ = changes; + } +} + +fn main() {} diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.rs b/tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.rs similarity index 100% rename from tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.rs rename to tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.rs diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr similarity index 94% rename from tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr index bc545b40c6a0e..ef28e406130e1 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/hidden-lifetimes.rs:38:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:38:5 | LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a { | -- -------------- opaque type defined here @@ -14,7 +14,7 @@ LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a + use<' | ++++++++++++++++ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/hidden-lifetimes.rs:55:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:55:5 | LL | fn hide_rc_refcell<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { | -- -------------- opaque type defined here diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr similarity index 91% rename from tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr index 5165e627022c1..bca6b764d371a 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/hidden-lifetimes.rs:46:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:46:5 | LL | hide_ref(&mut res).swap(hide_ref(&mut &x)); | -- `x` is borrowed here @@ -7,7 +7,7 @@ LL | res | ^^^ returns a value referencing data owned by the current function error[E0597]: `x` does not live long enough - --> $DIR/hidden-lifetimes.rs:62:38 + --> $DIR/rpit-hide-lifetime-for-swap.rs:62:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/impl-trait/hidden-lifetimes.rs b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs similarity index 100% rename from tests/ui/impl-trait/hidden-lifetimes.rs rename to tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..40450ab13a262 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:35:5 + | +LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..40450ab13a262 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:35:5 + | +LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs new file mode 100644 index 0000000000000..1381a87071f39 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs @@ -0,0 +1,40 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next +//@ [polonius_alpha] known-bug: #153215 +//@ [polonius_alpha] check-pass + +// Like rpit-hide-lifetime-for-swap, but the invariant lifetime is hidden +// inside a *type* argument of the opaque rather than being captured as a +// region argument. The hidden type can capture `U` (and thus all of its +// regions) because of the `U: 'a` bound, so the regions inside the type +// argument must be considered live too. + +trait Swap: Sized { + fn swap(self, other: Self); +} + +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} + +fn hide<'a, U: Swap + 'a>(_: &'a u8, x: U) -> impl Swap + 'a { + x +} + +fn dangle() -> &'static [i32; 3] { + let lock = 0u8; + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` +} + +fn main() { + println!("{:?}", dangle()); +} diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..f68ebd1610a13 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpitit-hide-lifetime-for-swap.rs:33:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..f68ebd1610a13 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpitit-hide-lifetime-for-swap.rs:33:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs new file mode 100644 index 0000000000000..fcf949a3e945e --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs @@ -0,0 +1,40 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] known-bug: #153215 +//@ [polonius_alpha] check-pass +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +// Test to show what happens if we were not careful and allowed invariant +// lifetimes to escape though an impl trait. +// +// Specifically we swap a long lived and short lived reference, giving us a +// dangling pointer. + +trait Swap: Sized { + fn swap(self, other: Self); +} +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} +trait Hider { + fn hide_ref<'a, 'b, T: 'static>(&self, x: &'a mut &'b T) -> impl Swap + 'a { + x + } +} +fn dangle_ref(h: &H) -> &'static [i32; 3] { + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` +} +struct H; +impl Hider for H {} + +fn main() { + println!("{:?}", dangle_ref(&H)); +} diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.rs b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.rs similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.rs rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.rs diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.stderr b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.stderr rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.stderr diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.rs b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.rs similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.rs rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.rs diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.stderr b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.stderr rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.stderr diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr deleted file mode 100644 index 48efb0ca558c0..0000000000000 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/rpit-hide-lifetime-for-swap.rs:26:5 - | -LL | fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { - | -- -------------- opaque type defined here - | | - | hidden type `Rc>` captures the lifetime `'b` as defined here -LL | x - | ^ - | -help: add a `use<...>` bound to explicitly capture `'b` - | -LL | fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a + use<'a, 'b, T> { - | ++++++++++++++++ - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0700`. diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr deleted file mode 100644 index e4f5475cdb0a6..0000000000000 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0597]: `x` does not live long enough - --> $DIR/rpit-hide-lifetime-for-swap.rs:33:38 - | -LL | let x = [1, 2, 3]; - | - binding `x` declared here -LL | let short = Rc::new(RefCell::new(&x)); - | ^^ borrowed value does not live long enough -... -LL | let res: &'static [i32; 3] = *long.borrow(); - | ----------------- type annotation requires that `x` is borrowed for `'static` -LL | res -LL | } - | - `x` dropped here while still borrowed - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs deleted file mode 100644 index de5335be73dbe..0000000000000 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs +++ /dev/null @@ -1,42 +0,0 @@ -//@ revisions: edition2015 edition2024 polonius_alpha -//@ ignore-compare-mode-polonius (explicit revisions) -//@ [edition2015] edition: 2015 -//@ [edition2024] edition: 2024 -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass -//@ [polonius_alpha] edition: 2024 -//@ [polonius_alpha] compile-flags: -Zpolonius=next - -// This test should never pass! - -use std::cell::RefCell; -use std::rc::Rc; - -trait Swap: Sized { - fn swap(self, other: Self); -} - -impl Swap for Rc> { - fn swap(self, other: Self) { - >::swap(&self, &other); - } -} - -fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { - x - //[edition2015]~^ ERROR hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds -} - -fn dangle() -> &'static [i32; 3] { - let long = Rc::new(RefCell::new(&[4, 5, 6])); - let x = [1, 2, 3]; - let short = Rc::new(RefCell::new(&x)); - //[edition2024]~^ ERROR `x` does not live long enough - hide(long.clone()).swap(hide(short)); - let res: &'static [i32; 3] = *long.borrow(); - res -} - -fn main() { - println!("{:?}", dangle()); -} From 4e176895ceac866f16f18e4cb9cd7fd79b307393 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Thu, 30 Apr 2026 03:10:16 +0000 Subject: [PATCH 02/78] Consider captured regions for opaque type region liveness. --- .../src/type_check/liveness/trace.rs | 4 +- .../rustc_hir_analysis/src/check/wfcheck.rs | 70 +--- .../rustc_infer/src/infer/outlives/mod.rs | 1 - .../src/rmeta/decoder/cstore_impl.rs | 1 + compiler/rustc_metadata/src/rmeta/encoder.rs | 1 + compiler/rustc_metadata/src/rmeta/mod.rs | 1 + .../rustc_metadata/src/rmeta/parameterized.rs | 6 +- compiler/rustc_middle/src/queries.rs | 18 + compiler/rustc_trait_selection/src/regions.rs | 73 ++++- .../rustc_trait_selection/src/traits/mod.rs | 3 + .../src/traits/outlives_for_liveness.rs | 310 ++++++++++++++++++ ...-hide-lifetime-for-swap.edition2015.stderr | 4 +- ...-hide-lifetime-for-swap.edition2024.stderr | 4 +- ...de-lifetime-for-swap.polonius_alpha.stderr | 26 ++ .../rpit-hide-lifetime-for-swap.rs | 6 +- 15 files changed, 447 insertions(+), 81 deletions(-) create mode 100644 compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs create mode 100644 tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 840210496eb44..25dc718003855 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -2,7 +2,6 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_index::bit_set::DenseBitSet; use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; -use rustc_infer::infer::outlives::for_liveness; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, HasLocalDecls, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; use rustc_middle::ty::relate::Relate; @@ -14,6 +13,7 @@ use rustc_mir_dataflow::{Analysis, 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, TypeOp, TypeOpOutput}; use tracing::debug; @@ -611,7 +611,7 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { values::pretty_print_points(location_map, live_at.iter()), ); - value.visit_with(&mut for_liveness::FreeRegionsVisitor { + value.visit_with(&mut FreeRegionsVisitor { tcx: typeck.tcx(), param_env: typeck.infcx.param_env, op: |r| { diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index bf8593eb61882..b16095e7b0658 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -13,8 +13,8 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_hir::{AmbigArg, ItemKind, find_attr}; +use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::outlives::env::OutlivesEnvironment; -use rustc_infer::infer::{self, InferCtxt, SubregionOrigin, TyCtxtInferExt}; use rustc_infer::traits::PredicateObligations; use rustc_lint_defs::builtin::SHADOWING_SUPERTRAIT_ITEMS; use rustc_macros::Diagnostic; @@ -30,7 +30,9 @@ use rustc_middle::{bug, span_bug}; use rustc_session::errors::feature_err; use rustc_span::{DUMMY_SP, Span, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; -use rustc_trait_selection::regions::{InferCtxtRegionExt, OutlivesEnvironmentBuildExt}; +use rustc_trait_selection::regions::{ + InferCtxtRegionExt, OutlivesEnvironmentBuildExt, region_known_to_outlive, ty_known_to_outlive, +}; use rustc_trait_selection::traits::misc::{ ConstParamTyImplementationError, type_allowed_to_implement_const_param_ty, }; @@ -691,70 +693,6 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( Some(bounds) } -/// Given a known `param_env` and a set of well formed types, can we prove that -/// `ty` outlives `region`. -fn ty_known_to_outlive<'tcx>( - tcx: TyCtxt<'tcx>, - id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - wf_tys: &FxIndexSet>, - ty: Ty<'tcx>, - region: ty::Region<'tcx>, -) -> bool { - test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { - infcx.register_type_outlives_constraint_inner(infer::TypeOutlivesConstraint { - sub_region: region, - sup_type: ty, - origin: SubregionOrigin::RelateParamBound(DUMMY_SP, ty, None), - }); - }) -} - -/// Given a known `param_env` and a set of well formed types, can we prove that -/// `region_a` outlives `region_b` -fn region_known_to_outlive<'tcx>( - tcx: TyCtxt<'tcx>, - id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - wf_tys: &FxIndexSet>, - region_a: ty::Region<'tcx>, - region_b: ty::Region<'tcx>, -) -> bool { - test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { - infcx.sub_regions( - SubregionOrigin::RelateRegionParamBound(DUMMY_SP, None), - region_b, - region_a, - ty::VisibleForLeakCheck::Unreachable, - ); - }) -} - -/// Given a known `param_env` and a set of well formed types, set up an -/// `InferCtxt`, call the passed function (to e.g. set up region constraints -/// to be tested), then resolve region and return errors -fn test_region_obligations<'tcx>( - tcx: TyCtxt<'tcx>, - id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - wf_tys: &FxIndexSet>, - add_constraints: impl FnOnce(&InferCtxt<'tcx>), -) -> bool { - // Unfortunately, we have to use a new `InferCtxt` each call, because - // region constraints get added and solved there and we need to test each - // call individually. - let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); - - add_constraints(&infcx); - - let errors = infcx.resolve_regions(id, param_env, wf_tys.iter().copied()); - debug!(?errors, "errors"); - - // If we were able to prove that the type outlives the region without - // an error, it must be because of the implied or explicit bounds... - errors.is_empty() -} - /// TypeVisitor that looks for uses of GATs like /// `>::GAT` and adds the arguments `P0..Pm` into /// the two vectors, `regions` and `types` (depending on their kind). For each diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index 92b47295ade88..76db3830d3962 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -16,7 +16,6 @@ use crate::infer::lexical_region_resolve; use crate::infer::region_constraints::ConstraintKind; pub mod env; -pub mod for_liveness; pub mod obligations; pub mod test_type_match; pub(crate) mod verify; diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index bc1c1d8240559..7016273da3815 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -417,6 +417,7 @@ provide! { tcx, def_id, other, cdata, } anon_const_kind => { table } const_of_item => { table } + live_regions_for_opaque_from_outlives_bounds => { table } } pub(in crate::rmeta) fn provide(providers: &mut Providers) { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index c232f595d4229..4a4bf4fc057b5 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1623,6 +1623,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); } + record!(self.tables.live_regions_for_opaque_from_outlives_bounds[def_id] <- tcx.live_regions_for_opaque_from_outlives_bounds(def_id)); } if let DefKind::AnonConst = def_kind { record!(self.tables.anon_const_kind[def_id] <- self.tcx.anon_const_kind(def_id)); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 90721f0f1fc11..b784e59426308 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -480,6 +480,7 @@ define_tables! { anon_const_kind: Table>, const_of_item: Table>>>, associated_types_for_impl_traits_in_trait_or_impl: Table>>>, + live_regions_for_opaque_from_outlives_bounds: Table>>>>>, } #[derive(TyEncodable, TyDecodable)] diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 40f1c85417b04..76528e04944f0 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use rustc_data_structures::unord::UnordMap; use rustc_hir::def_id::DefIndex; use rustc_index::{Idx, IndexVec}; -use rustc_middle::ty::{Binder, EarlyBinder}; +use rustc_middle::ty::{Binder, EarlyBinder, Region}; use rustc_span::Symbol; use crate::rmeta::{LazyArray, LazyValue}; @@ -48,6 +48,10 @@ impl ParameterizedOverTcx for LazyArray { type Value<'tcx> = LazyArray>; } +impl ParameterizedOverTcx for Region<'static> { + type Value<'tcx> = Region<'tcx>; +} + macro_rules! trivially_parameterized_over_tcx { ($($ty:ty),+ $(,)?) => { $( diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index f0557c3d3381a..d9c2599407950 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2122,6 +2122,24 @@ rustc_queries! { desc { "listing captured lifetimes for opaque `{}`", tcx.def_path_str(def_id) } } + /// For an opaque type, return the list of potentially live args from the set of outlives bounds on that opaque. + /// ```ignore (illustrative) + /// // Edition 2024: all args are captured + /// fn foo<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized + 'a {} + /// fn bar<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized + 'static {} + /// fn baz<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized {} + /// ``` + /// + /// In the above: + /// - `foo` outlives `'a`, but we know that `'b: 'a` holds, so `'b` is *also* potentially live + /// - `bar` outlives `'static`, so we know that all lifetimes are potentially live and we can return an empty set + /// - `baz` has not outlives bound, so return `None` and let the caller decide what to do + query live_regions_for_opaque_from_outlives_bounds(def_id: DefId) -> &'tcx Option>>> { + arena_cache + desc { "identifying live args for opaque `{}`", tcx.def_path_str(def_id) } + separate_provide_extern + } + /// Computes the visibility of the provided `def_id`. /// /// If the item from the `def_id` doesn't have a visibility, it will panic. For example diff --git a/compiler/rustc_trait_selection/src/regions.rs b/compiler/rustc_trait_selection/src/regions.rs index 866be1e532661..64de92b8765a0 100644 --- a/compiler/rustc_trait_selection/src/regions.rs +++ b/compiler/rustc_trait_selection/src/regions.rs @@ -1,11 +1,14 @@ +use rustc_data_structures::fx::FxIndexSet; use rustc_hir::def_id::LocalDefId; use rustc_infer::infer::outlives::env::OutlivesEnvironment; -use rustc_infer::infer::{InferCtxt, RegionResolutionError}; +use rustc_infer::infer::{ + InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt, TypeOutlivesConstraint, +}; use rustc_macros::extension; use rustc_middle::traits::ObligationCause; use rustc_middle::traits::query::NoSolution; -use rustc_middle::ty::{self, Ty, Unnormalized, elaborate}; -use rustc_span::Span; +use rustc_middle::ty::{self, Ty, TyCtxt, TypingMode, Unnormalized, elaborate}; +use rustc_span::{DUMMY_SP, Span}; use crate::traits::ScrubbedTraitError; use crate::traits::outlives_bounds::InferCtxtExt; @@ -120,3 +123,67 @@ impl<'tcx> InferCtxt<'tcx> { ) } } + +/// Given a known `param_env` and a set of well formed types, can we prove that +/// `ty` outlives `region`. +pub fn ty_known_to_outlive<'tcx>( + tcx: TyCtxt<'tcx>, + id: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + wf_tys: &FxIndexSet>, + ty: Ty<'tcx>, + region: ty::Region<'tcx>, +) -> bool { + test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { + infcx.register_type_outlives_constraint_inner(TypeOutlivesConstraint { + sub_region: region, + sup_type: ty, + origin: SubregionOrigin::RelateParamBound(DUMMY_SP, ty, None), + }); + }) +} + +/// Given a known `param_env` and a set of well formed types, can we prove that +/// `region_a` outlives `region_b` +pub fn region_known_to_outlive<'tcx>( + tcx: TyCtxt<'tcx>, + id: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + wf_tys: &FxIndexSet>, + region_a: ty::Region<'tcx>, + region_b: ty::Region<'tcx>, +) -> bool { + test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { + infcx.sub_regions( + SubregionOrigin::RelateRegionParamBound(DUMMY_SP, None), + region_b, + region_a, + ty::VisibleForLeakCheck::Unreachable, + ); + }) +} + +/// Given a known `param_env` and a set of well formed types, set up an +/// `InferCtxt`, call the passed function (to e.g. set up region constraints +/// to be tested), then resolve region and return errors +pub fn test_region_obligations<'tcx>( + tcx: TyCtxt<'tcx>, + id: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + wf_tys: &FxIndexSet>, + add_constraints: impl FnOnce(&InferCtxt<'tcx>), +) -> bool { + // Unfortunately, we have to use a new `InferCtxt` each call, because + // region constraints get added and solved there and we need to test each + // call individually. + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + + add_constraints(&infcx); + + let errors = infcx.resolve_regions(id, param_env, wf_tys.iter().copied()); + tracing::debug!(?errors, "errors"); + + // If we were able to prove that the type outlives the region without + // an error, it must be because of the implied or explicit bounds... + errors.is_empty() +} diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index c062198861ebe..9d3062c4296e0 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -12,6 +12,7 @@ mod fulfill; pub mod misc; pub mod normalize; pub mod outlives_bounds; +pub mod outlives_for_liveness; pub mod project; pub mod query; #[allow(hidden_glob_reexports)] @@ -947,6 +948,8 @@ pub fn provide(providers: &mut Providers) { specialization_enabled_in: specialize::specialization_enabled_in, instantiate_and_check_impossible_predicates, is_impossible_associated_item, + live_regions_for_opaque_from_outlives_bounds: + outlives_for_liveness::live_regions_for_opaque_from_outlives_bounds, ..*providers }; } diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs new file mode 100644 index 0000000000000..92193ddc4b797 --- /dev/null +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -0,0 +1,310 @@ +use rustc_data_structures::indexmap::IndexSet; +use rustc_hir::def_id::LocalDefId; +use rustc_middle::ty::{ + self, Flags, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, + Unnormalized, +}; + +use crate::infer::outlives::test_type_match; +use crate::infer::region_constraints::VerifyIfEq; +use crate::regions::region_known_to_outlive; + +/// For a given opaque type, this returns the set of generic args that are relevant for liveness, that can be inferred +/// from outlives bounds on the opaque. +/// +/// There are three cases to consider: +/// 1. If there are *no* outlives bounds, then we return None. +/// 2. If there is a `'static` outlives bound, then we know that all regions are irrelevant, so we return an empty list. +/// 3. If there are *any* outlives bounds, Then we find any args that outlive those bounds. +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn live_regions_for_opaque_from_outlives_bounds<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> Option>>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + + // We first want to collect the outlives bounds of the opaque. + let bounds = tcx.item_bounds(def_id).instantiate_identity().skip_norm_wip(); + tracing::debug!(?bounds); + let alias_ty = Ty::new_opaque(tcx, ty::IsRigid::No, def_id.to_def_id(), self_identity_args); + let opaque_outlives_regions: Vec<_> = bounds + .iter() + .filter_map(|clause| { + let outlives = clause.as_type_outlives_clause()?; + if let Some(outlives) = outlives.no_bound_vars() + && outlives.0 == alias_ty + { + Some(outlives.1) + } else { + test_type_match::extract_verify_if_eq( + tcx, + &outlives + .map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }), + // FIXME(#155345): Region handling should generally only + // deal with rigid aliases, making sure we do so correctly + // everywhere is effort, so we're just using `No` everywhere + // for now. This should change soon. + alias_ty, + ) + } + }) + .collect(); + tracing::debug!(?opaque_outlives_regions); + + // If there are no outlives bounds, then all (non-bivariant) args are potentially live. + if opaque_outlives_regions.is_empty() { + return None; + } + + // If any of the outlives bounds are `'static`, then we know the opaque doesn't capture + // *any* regions, so we can skip visiting any regions at all. + // + // Thinking about it, I was originally a bit concerned about something like `'a: 'static`, and + // whether or not we need to mark `'a` as live. I don't think *today* we do, since I think regions + // that outlive `'static` are special enough, but I *could* imagine some world where we need to be + // more careful about this. Given I can't find a test that goes wrong, I'm going to leave in this + // optimization. + if opaque_outlives_regions.contains(&tcx.lifetimes.re_static) { + tracing::debug!("opaque has a 'static outlives bound, so skipping visiting any regions"); + return Some(ty::EarlyBinder::bind(tcx, vec![])); + } + + // Okay, so we know we have some outlives bounds, and that none of them are `'static`. + // Now, we need to find all other potentially-live regions, + // those that outlive an outlives-bound region and are captured. + // We will map both the opaque outlives regions and the set of captured regions + // back to the parent, and then use all bounds (explicit and implied) to + // find the set of captured regions that outlive the outlives bounds. + + let opaque_captured_lifetimes = tcx.opaque_captured_lifetimes(def_id); + tracing::debug!(?opaque_captured_lifetimes); + + // Map the outlives regions to the parent regions + let generics = tcx.generics_of(def_id); + let parent_outlives_regions: Vec<_> = opaque_outlives_regions + .iter() + .map(|opaque_region| { + let region_def_id = match opaque_region.kind() { + ty::ReEarlyParam(ebr) => generics.param_at(ebr.index as usize, tcx).def_id, + _ => panic!("unexpected region `{opaque_region}` in opaque bounds"), + }; + let region_param = + generics.own_params.iter().find(|param| param.def_id == region_def_id).unwrap(); + let (_, opaque_region_def_id) = opaque_captured_lifetimes + .iter() + .find(|(_, opaque_r)| opaque_r.to_def_id() == region_def_id) + .unwrap(); + let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_region_def_id); + tracing::debug!(?region_def_id, ?region_param, ?parent_region); + parent_region + }) + .collect(); + tracing::debug!(?parent_outlives_regions); + + // Map the captured regions to the parent regions + let mut parent_captured_regions: Vec<(ty::Region<'tcx>, LocalDefId)> = + Vec::with_capacity(opaque_captured_lifetimes.len()); + for (_, opaque_lt) in opaque_captured_lifetimes.iter() { + let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_lt); + parent_captured_regions.push((parent_region, *opaque_lt)); + } + tracing::debug!(?parent_captured_regions); + + // For implied bounds, we need the set of WF types from the parents. + // - For functions, this is all the input and output types. + // - For type alias, there are no implied bounds, so this is empty. (FIXME: the alias type itself probably should be here?) + let (parent_def_id, wf_tys) = match tcx.opaque_ty_origin(def_id) { + rustc_hir::OpaqueTyOrigin::FnReturn { parent, .. } + | rustc_hir::OpaqueTyOrigin::AsyncFn { parent, .. } => { + let fn_sig = tcx.fn_sig(parent).instantiate_identity().skip_norm_wip(); + let (liberated_fn_sig, _) = tcx.instantiate_bound_regions(fn_sig, |br| { + let kind = ty::LateParamRegionKind::from_bound(br.var, br.kind); + ty::Region::new_late_param(tcx, parent, kind) + }); + let wf_tys = liberated_fn_sig.inputs_and_output.iter().collect(); + (parent, wf_tys) + } + rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => (parent, IndexSet::default()), + }; + + // Find all the opaque's captured regions that outlive the outlives bounds using the implied bounds + let parent_param_env = tcx.param_env(parent_def_id); + tracing::debug!(?parent_param_env); + let mut opaque_live_regions = Vec::with_capacity(parent_outlives_regions.len()); + for (parent_captured_region, opaque_captured_def_id) in parent_captured_regions.iter() { + let mut all_outlives = true; + for parent_outlives_region in parent_outlives_regions.iter() { + let known_outlives = region_known_to_outlive( + tcx, + parent_def_id.expect_local(), + parent_param_env, + &wf_tys, + *parent_captured_region, + *parent_outlives_region, + ); + tracing::debug!(?parent_captured_region, ?parent_outlives_region, ?known_outlives); + if !known_outlives { + all_outlives = false; + break; + } + } + + if all_outlives { + let param = generics + .own_params + .iter() + .find(|param| param.def_id == opaque_captured_def_id.to_def_id()) + .unwrap(); + let opaque_region = + ty::Region::new_early_param(tcx, param.to_early_bound_region_data()); + opaque_live_regions.push(opaque_region); + } + } + tracing::debug!(?opaque_live_regions); + + Some(ty::EarlyBinder::bind(tcx, opaque_live_regions)) +} + +/// Visits free regions in the type that are relevant for liveness computation. +/// These regions are passed to `OP`. +/// +/// Specifically, we visit all of the regions of types recursively, except if +/// the type is an alias, we look at the outlives bounds in the param-env +/// and alias's item bounds. If there is a unique outlives bound, then visit +/// that instead. If there is not a unique but there is a `'static` outlives +/// bound, then don't visit anything. Otherwise, walk through the opaque's +/// regions structurally. +pub struct FreeRegionsVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> { + pub tcx: TyCtxt<'tcx>, + pub param_env: ty::ParamEnv<'tcx>, + pub op: OP, +} + +impl<'tcx, OP> TypeVisitor> for FreeRegionsVisitor<'tcx, OP> +where + OP: FnMut(ty::Region<'tcx>), +{ + fn visit_region(&mut self, r: ty::Region<'tcx>) { + match r.kind() { + // ignore bound regions, keep visiting + ty::ReBound(_, _) => {} + _ => (self.op)(r), + } + } + + #[tracing::instrument(skip(self), level = "debug")] + fn visit_ty(&mut self, ty: Ty<'tcx>) { + // We're only interested in types involving regions + if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { + return; + } + + // FIXME: Don't consider alias bounds on types that have escaping bound + // vars. See #117455. + if ty.has_escaping_bound_vars() { + return ty.super_visit_with(self); + } + + match *ty.kind() { + // We can prove that an alias is live two ways: + // 1. All the components are live. + // + // 2. There is a known outlives bound or where-clause, and that + // region is live. + // + // We search through the item bounds and where clauses for + // either `'static` or a unique outlives region, and if one is + // found, we just need to prove that that region is still live. + // If one is not found, then we continue to walk through the alias. + ty::Alias(_, alias_ty @ ty::AliasTy { kind, args, .. }) => { + let tcx = self.tcx; + let param_env = self.param_env; + + // Opaques are special, because there are additional captured regions that we need to consider. + if let ty::AliasTyKind::Opaque { def_id } = kind { + let opaque_outlives_args = + tcx.live_regions_for_opaque_from_outlives_bounds(def_id); + + match opaque_outlives_args { + Some(opaque_live_regions) => { + for r in opaque_live_regions.as_ref().skip_binder() { + let r = ty::EarlyBinder::bind(tcx, *r) + .instantiate(tcx, args) + .skip_norm_wip(); + r.visit_with(self); + } + } + None => { + let variances = tcx.variances_of(def_id); + for (idx, s) in args.iter().enumerate() { + if variances[idx] != ty::Bivariant { + s.visit_with(self); + } + } + } + } + + return; + } + + let def_id = match kind { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + let outlives_bounds: Vec<_> = tcx + .item_bounds(def_id) + .iter_instantiated(tcx, args) + .map(Unnormalized::skip_norm_wip) + .chain(param_env.caller_bounds()) + .filter_map(|clause| { + let outlives = clause.as_type_outlives_clause()?; + if let Some(outlives) = outlives.no_bound_vars() + && outlives.0 == ty + { + Some(outlives.1) + } else { + test_type_match::extract_verify_if_eq( + tcx, + &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { + VerifyIfEq { ty, bound } + }), + // FIXME(#155345): Region handling should generally only + // deal with rigid aliases, making sure we do so correctly + // everywhere is effort, so we're just using `No` everywhere + // for now. This should change soon. + alias_ty.to_ty(tcx, ty::IsRigid::No), + ) + } + }) + .collect(); + tracing::debug!(?outlives_bounds); + // If we find `'static`, then we know the alias doesn't capture *any* regions. + // Otherwise, all of the outlives regions should be equal -- if they're not, + // we don't really know how to proceed, so we continue recursing through the + // alias. + if outlives_bounds.contains(&tcx.lifetimes.re_static) { + // no + } else if let Some(r) = outlives_bounds.first() + && outlives_bounds[1..].iter().all(|other_r| other_r == r) + { + assert!(r.type_flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS)); + r.visit_with(self); + } else { + // Skip lifetime parameters that are not captured, since they do + // not need to be live. + let variances = tcx.opt_alias_variances(kind); + + for (idx, s) in args.iter().enumerate() { + if variances.map(|variances| variances[idx]) != Some(ty::Bivariant) { + s.visit_with(self); + } + } + } + } + + _ => ty.super_visit_with(self), + } + } +} diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr index ef28e406130e1..ac839b0c5dc48 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/rpit-hide-lifetime-for-swap.rs:38:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:36:5 | LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a { | -- -------------- opaque type defined here @@ -14,7 +14,7 @@ LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a + use<' | ++++++++++++++++ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/rpit-hide-lifetime-for-swap.rs:55:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:53:5 | LL | fn hide_rc_refcell<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { | -- -------------- opaque type defined here diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr index bca6b764d371a..17916dac8afd7 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/rpit-hide-lifetime-for-swap.rs:46:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:44:5 | LL | hide_ref(&mut res).swap(hide_ref(&mut &x)); | -- `x` is borrowed here @@ -7,7 +7,7 @@ LL | res | ^^^ returns a value referencing data owned by the current function error[E0597]: `x` does not live long enough - --> $DIR/rpit-hide-lifetime-for-swap.rs:62:38 + --> $DIR/rpit-hide-lifetime-for-swap.rs:60:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..17916dac8afd7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,26 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-for-swap.rs:44:5 + | +LL | hide_ref(&mut res).swap(hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error[E0597]: `x` does not live long enough + --> $DIR/rpit-hide-lifetime-for-swap.rs:60:38 + | +LL | let x = [1, 2, 3]; + | - binding `x` declared here +LL | let short = Rc::new(RefCell::new(&x)); + | ^^ borrowed value does not live long enough +LL | hide_rc_refcell(long.clone()).swap(hide_rc_refcell(short)); +LL | let res: &'static [i32; 3] = *long.borrow(); + | ----------------- type annotation requires that `x` is borrowed for `'static` +LL | res +LL | } + | - `x` dropped here while still borrowed + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0515, E0597. +For more information about an error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs index d113953c5bbc7..e03d4452dd085 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs @@ -2,8 +2,6 @@ //@ ignore-compare-mode-polonius (explicit revisions) //@ [edition2015] edition: 2015 //@ [edition2024] edition: 2024 -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next @@ -43,7 +41,7 @@ fn dangle_ref() -> &'static [i32; 3] { let mut res = &[4, 5, 6]; let x = [1, 2, 3]; hide_ref(&mut res).swap(hide_ref(&mut &x)); - res //[edition2024]~ ERROR cannot return value referencing local variable `x` + res //[edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` } // Here we are hiding `'b` making the caller believe that `Rc>` @@ -59,7 +57,7 @@ fn hide_rc_refcell<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + fn dangle_rc_refcell() -> &'static [i32; 3] { let long = Rc::new(RefCell::new(&[4, 5, 6])); let x = [1, 2, 3]; - let short = Rc::new(RefCell::new(&x)); //[edition2024]~ ERROR `x` does not live long enough + let short = Rc::new(RefCell::new(&x)); //[edition2024,polonius_alpha]~ ERROR `x` does not live long enough hide_rc_refcell(long.clone()).swap(hide_rc_refcell(short)); let res: &'static [i32; 3] = *long.borrow(); res From 9ab5a940246bb980107b1b3fc25c442440b195f4 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Thu, 11 Jun 2026 18:47:21 +0000 Subject: [PATCH 03/78] Also extend liveness rules for non-opaque aliases --- .../src/rmeta/decoder/cstore_impl.rs | 3 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 18 +- compiler/rustc_metadata/src/rmeta/mod.rs | 3 +- .../rustc_metadata/src/rmeta/parameterized.rs | 7 +- compiler/rustc_middle/src/queries.rs | 23 +- .../rustc_trait_selection/src/traits/mod.rs | 6 +- .../src/traits/outlives_for_liveness.rs | 459 +++++++++++++++--- ...-hide-lifetime-for-swap.edition2015.stderr | 2 +- ...-hide-lifetime-for-swap.edition2024.stderr | 2 +- ...de-lifetime-for-swap.polonius_alpha.stderr | 11 + .../gat-hide-lifetime-for-swap.rs | 4 +- ...me-in-type-arg-for-swap.edition2015.stderr | 2 +- ...me-in-type-arg-for-swap.edition2024.stderr | 2 +- ...in-type-arg-for-swap.polonius_alpha.stderr | 11 + .../gat-hide-lifetime-in-type-arg-for-swap.rs | 4 +- ...me-in-type-arg-for-swap.edition2015.stderr | 2 +- ...me-in-type-arg-for-swap.edition2024.stderr | 2 +- ...in-type-arg-for-swap.polonius_alpha.stderr | 11 + ...rpit-hide-lifetime-in-type-arg-for-swap.rs | 4 +- 19 files changed, 475 insertions(+), 101 deletions(-) create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr create mode 100644 tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr create mode 100644 tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 7016273da3815..e447dace2343d 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -417,7 +417,8 @@ provide! { tcx, def_id, other, cdata, } anon_const_kind => { table } const_of_item => { table } - live_regions_for_opaque_from_outlives_bounds => { table } + live_args_for_alias_from_outlives_bounds => { table } + args_known_to_outlive_alias_params => { table } } pub(in crate::rmeta) fn provide(providers: &mut Providers) { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 4a4bf4fc057b5..1348c5a67222e 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1613,6 +1613,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.tables .type_alias_is_lazy .set(def_id.index, self.tcx.type_alias_is_lazy(def_id)); + if self.tcx.type_alias_is_lazy(def_id) { + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } } if let DefKind::OpaqueTy = def_kind { self.encode_explicit_item_bounds(def_id); @@ -1623,7 +1626,20 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); } - record!(self.tables.live_regions_for_opaque_from_outlives_bounds[def_id] <- tcx.live_regions_for_opaque_from_outlives_bounds(def_id)); + record!(self.tables.live_args_for_alias_from_outlives_bounds[def_id] <- tcx.live_args_for_alias_from_outlives_bounds(def_id)); + } + if let DefKind::AssocTy = def_kind { + let assoc_item = tcx.associated_item(def_id); + match assoc_item.container { + ty::AssocContainer::Trait => { + record!(self.tables.live_args_for_alias_from_outlives_bounds[def_id] <- tcx.live_args_for_alias_from_outlives_bounds(def_id)); + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } + ty::AssocContainer::InherentImpl => { + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } + ty::AssocContainer::TraitImpl(_) => {} + } } if let DefKind::AnonConst = def_kind { record!(self.tables.anon_const_kind[def_id] <- self.tcx.anon_const_kind(def_id)); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index b784e59426308..cccd613aa8ac1 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -480,7 +480,8 @@ define_tables! { anon_const_kind: Table>, const_of_item: Table>>>, associated_types_for_impl_traits_in_trait_or_impl: Table>>>, - live_regions_for_opaque_from_outlives_bounds: Table>>>>>, + live_args_for_alias_from_outlives_bounds: Table>>>>>, + args_known_to_outlive_alias_params: Table, Vec>)>>>>, } #[derive(TyEncodable, TyDecodable)] diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 76528e04944f0..feb6859eabd5b 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use rustc_data_structures::unord::UnordMap; use rustc_hir::def_id::DefIndex; use rustc_index::{Idx, IndexVec}; -use rustc_middle::ty::{Binder, EarlyBinder, Region}; +use rustc_middle::ty::{Binder, EarlyBinder, GenericArg, Region}; use rustc_span::Symbol; use crate::rmeta::{LazyArray, LazyValue}; @@ -52,6 +52,10 @@ impl ParameterizedOverTcx for Region<'static> { type Value<'tcx> = Region<'tcx>; } +impl ParameterizedOverTcx for GenericArg<'static> { + type Value<'tcx> = GenericArg<'tcx>; +} + macro_rules! trivially_parameterized_over_tcx { ($($ty:ty),+ $(,)?) => { $( @@ -65,6 +69,7 @@ macro_rules! trivially_parameterized_over_tcx { trivially_parameterized_over_tcx! { bool, + u32, u64, usize, std::string::String, diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index d9c2599407950..a4586b79eeffb 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2122,7 +2122,9 @@ rustc_queries! { desc { "listing captured lifetimes for opaque `{}`", tcx.def_path_str(def_id) } } - /// For an opaque type, return the list of potentially live args from the set of outlives bounds on that opaque. + /// For an opaque type or trait associated type, return the list of potentially live + /// (identity) generic args from the set of outlives bounds on that alias. Callers should + /// instantiate the returned args with the concrete args of the alias. /// ```ignore (illustrative) /// // Edition 2024: all args are captured /// fn foo<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized + 'a {} @@ -2132,11 +2134,22 @@ rustc_queries! { /// /// In the above: /// - `foo` outlives `'a`, but we know that `'b: 'a` holds, so `'b` is *also* potentially live - /// - `bar` outlives `'static`, so we know that all lifetimes are potentially live and we can return an empty set - /// - `baz` has not outlives bound, so return `None` and let the caller decide what to do - query live_regions_for_opaque_from_outlives_bounds(def_id: DefId) -> &'tcx Option>>> { + /// (and so is `T`, since `T: 'static` implies `T: 'a`) + /// - `bar` outlives `'static`, so we know that no args are potentially live and we can return an empty set + /// - `baz` has no outlives bound, so return `None` and let the caller decide what to do + query live_args_for_alias_from_outlives_bounds(def_id: DefId) -> &'tcx Option>>> { arena_cache - desc { "identifying live args for opaque `{}`", tcx.def_path_str(def_id) } + desc { "identifying live args for alias `{}`", tcx.def_path_str(def_id) } + separate_provide_extern + } + + /// For each region param of an alias, the identity args that are known to + /// outlive it given only the alias's declared where-clauses. Used for liveness: + /// these are the only args whose regions the underlying type of the alias + /// could capture while satisfying an outlives bound on that param. + query args_known_to_outlive_alias_params(def_id: DefId) -> &'tcx ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + arena_cache + desc { "computing the args known to outlive each region param of alias `{}`", tcx.def_path_str(def_id) } separate_provide_extern } diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 9d3062c4296e0..8b74b9039fd7f 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -948,8 +948,10 @@ pub fn provide(providers: &mut Providers) { specialization_enabled_in: specialize::specialization_enabled_in, instantiate_and_check_impossible_predicates, is_impossible_associated_item, - live_regions_for_opaque_from_outlives_bounds: - outlives_for_liveness::live_regions_for_opaque_from_outlives_bounds, + live_args_for_alias_from_outlives_bounds: + outlives_for_liveness::live_args_for_alias_from_outlives_bounds, + args_known_to_outlive_alias_params: + outlives_for_liveness::args_known_to_outlive_alias_params, ..*providers }; } diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs index 92193ddc4b797..6695b987d3e44 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -1,26 +1,49 @@ -use rustc_data_structures::indexmap::IndexSet; -use rustc_hir::def_id::LocalDefId; +use rustc_data_structures::fx::FxIndexSet; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_middle::bug; use rustc_middle::ty::{ self, Flags, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - Unnormalized, }; use crate::infer::outlives::test_type_match; use crate::infer::region_constraints::VerifyIfEq; -use crate::regions::region_known_to_outlive; +use crate::regions::{region_known_to_outlive, ty_known_to_outlive}; -/// For a given opaque type, this returns the set of generic args that are relevant for liveness, that can be inferred -/// from outlives bounds on the opaque. +/// For a given opaque type, this returns the set of (identity) generic args that are relevant +/// for liveness, that can be inferred from outlives bounds on the opaque. Callers should +/// instantiate the returned args with the concrete args of the alias. /// /// There are three cases to consider: /// 1. If there are *no* outlives bounds, then we return None. -/// 2. If there is a `'static` outlives bound, then we know that all regions are irrelevant, so we return an empty list. -/// 3. If there are *any* outlives bounds, Then we find any args that outlive those bounds. +/// 2. If there is a `'static` outlives bound, then we know that all args are irrelevant, so we return an empty list. +/// 3. If there are *any* outlives bounds, then we find any args that are known to outlive those +/// bounds -- those are the only args whose regions the underlying type could capture. +/// +/// Note that the returned args can include *type* args: e.g. given a `T: 'a` bound in the +/// defining scope, the underlying type of an opaque with an `'a` outlives bound can capture `T`, +/// and so all the regions of the instantiated type arg must be considered live. #[tracing::instrument(level = "debug", skip(tcx), ret)] -pub(crate) fn live_regions_for_opaque_from_outlives_bounds<'tcx>( +pub(crate) fn live_args_for_alias_from_outlives_bounds<'tcx>( tcx: TyCtxt<'tcx>, def_id: LocalDefId, -) -> Option>>> { +) -> Option>>> { + match tcx.def_kind(def_id) { + DefKind::AssocTy | DefKind::TyAlias => { + live_args_for_projection_from_outlives_bounds(tcx, def_id) + } + DefKind::OpaqueTy => live_args_for_opaque_from_outlives_bounds(tcx, def_id), + kind => { + bug!("improper def_kind {kind:?} passed to `live_args_for_alias_from_outlives_bounds`") + } + } +} + +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn live_args_for_opaque_from_outlives_bounds<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> Option>>> { let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); // We first want to collect the outlives bounds of the opaque. @@ -124,13 +147,18 @@ pub(crate) fn live_regions_for_opaque_from_outlives_bounds<'tcx>( let wf_tys = liberated_fn_sig.inputs_and_output.iter().collect(); (parent, wf_tys) } - rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => (parent, IndexSet::default()), + rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => (parent, FxIndexSet::default()), }; + // It certainly would be nice to use `args_known_to_outlive_alias_params` here, + // but unfortunately we need to check the outlives relationships in the *parent*, + // not the opaque itself. + // Find all the opaque's captured regions that outlive the outlives bounds using the implied bounds let parent_param_env = tcx.param_env(parent_def_id); tracing::debug!(?parent_param_env); - let mut opaque_live_regions = Vec::with_capacity(parent_outlives_regions.len()); + let mut live_args: Vec> = + Vec::with_capacity(parent_outlives_regions.len()); for (parent_captured_region, opaque_captured_def_id) in parent_captured_regions.iter() { let mut all_outlives = true; for parent_outlives_region in parent_outlives_regions.iter() { @@ -157,23 +185,309 @@ pub(crate) fn live_regions_for_opaque_from_outlives_bounds<'tcx>( .unwrap(); let opaque_region = ty::Region::new_early_param(tcx, param.to_early_bound_region_data()); - opaque_live_regions.push(opaque_region); + live_args.push(opaque_region.into()); + } + } + + // The underlying type can also capture the opaque's *type* params (which are inherited from + // the parent), if they are known to outlive the outlives bounds (e.g. from a `T: 'a` bound + // in the parent's environment). In that case, all the regions of the instantiated type arg + // must be considered live. The parent's *region* params don't need to be considered here: + // the captured ones are duplicated as the opaque's own params (handled above), and the + // parent positions are bivariant. + for arg in self_identity_args.iter() { + let Some(param_ty) = arg.as_type() else { + continue; + }; + let all_outlives = parent_outlives_regions.iter().all(|parent_outlives_region| { + let known_outlives = ty_known_to_outlive( + tcx, + parent_def_id.expect_local(), + parent_param_env, + &wf_tys, + param_ty, + *parent_outlives_region, + ); + tracing::debug!(?param_ty, ?parent_outlives_region, ?known_outlives); + known_outlives + }); + if all_outlives { + live_args.push(arg); } } - tracing::debug!(?opaque_live_regions); + tracing::debug!(?live_args); - Some(ty::EarlyBinder::bind(tcx, opaque_live_regions)) + Some(ty::EarlyBinder::bind(tcx, live_args)) +} + +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn live_args_for_projection_from_outlives_bounds<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> Option>>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + + // We first want to collect the outlives bounds of the opaque. + let bounds = tcx.item_bounds(def_id).instantiate_identity().skip_norm_wip(); + tracing::debug!(?bounds); + let alias_ty = Ty::new_projection(tcx, ty::IsRigid::No, def_id.to_def_id(), self_identity_args); + let alias_outlives_regions: Vec<_> = bounds + .iter() + .filter_map(|clause| { + let outlives = clause.as_type_outlives_clause()?; + if let Some(outlives) = outlives.no_bound_vars() + && outlives.0 == alias_ty + { + Some(outlives.1) + } else { + test_type_match::extract_verify_if_eq( + tcx, + &outlives + .map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }), + alias_ty, + ) + } + }) + .collect(); + tracing::debug!(?alias_outlives_regions); + + // If there are no outlives bounds, then all (non-bivariant) args are potentially live. + if alias_outlives_regions.is_empty() { + return None; + } + + // If any of the outlives bounds are `'static`, then we know the opaque doesn't capture + // *any* regions, so we can skip visiting any regions at all. + // + // Thinking about it, I was originally a bit concerned about something like `'a: 'static`, and + // whether or not we need to mark `'a` as live. I don't think *today* we do, since I think regions + // that outlive `'static` are special enough, but I *could* imagine some world where we need to be + // more careful about this. Given I can't find a test that goes wrong, I'm going to leave in this + // optimization. + if alias_outlives_regions.contains(&tcx.lifetimes.re_static) { + tracing::debug!("opaque has a 'static outlives bound, so skipping visiting any regions"); + return Some(ty::EarlyBinder::bind(tcx, vec![])); + } + + tracing::debug!(?alias_outlives_regions); + + // Find all the args that are known to outlive the outlives bounds given the assoc type's + // declared where-clauses -- those are the only args the underlying type could capture. Note + // that this includes *type* args (incl. `Self`): e.g. a declared `U: 'a` lets the underlying + // type capture `U`, so all the regions of the instantiated type arg must be considered live. + // Each bound restricts the set independently, so we take the intersection. + let args_known_to_outlive = tcx.args_known_to_outlive_alias_params(def_id.to_def_id()); + tracing::debug!(?args_known_to_outlive); + let mut live_args: Option>> = None; + for outlives_region in alias_outlives_regions { + let (_, outliving_args) = args_known_to_outlive + .as_ref() + .skip_binder() + .iter() + .find(|(region, _)| *region == outlives_region) + .unwrap(); + let outliving_args: FxIndexSet<_> = outliving_args.iter().copied().collect(); + live_args = Some(match live_args.take() { + None => outliving_args, + Some(prev) => prev.intersection(&outliving_args).copied().collect(), + }); + } + let live_args: Vec<_> = live_args.unwrap().into_iter().collect(); + tracing::debug!(?live_args); + + Some(ty::EarlyBinder::bind(tcx, live_args)) +} + +/// For each region param of this alias (identified by its index into the alias's generics), +/// compute the identity args that are known to outlive it, given only the alias's declared +/// where-clauses. These are the only args whose regions the underlying type of the alias +/// could capture while satisfying an outlives bound on that param. +/// +/// Note that the outliving args can include *type* args (incl. `Self`): e.g. a declared +/// `U: 'a` bound means `U` is known to outlive `'a`, so the underlying type can capture `U`. +/// Const args are never included: const param types can't contain (non-`'static`) regions, +/// so they're irrelevant for liveness. +// FIXME: this likely should return a `BitSet` instead of a `Vec>` +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn args_known_to_outlive_alias_params<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + let param_env = tcx.param_env(def_id); + tracing::debug!(?param_env); + let wf_tys = FxIndexSet::default(); + let mut result = Vec::new(); + for outlived_arg in self_identity_args.iter() { + let Some(outlived_region) = outlived_arg.as_region() else { + continue; + }; + let outliving_args = self_identity_args + .iter() + .filter(|arg| match arg.kind() { + ty::GenericArgKind::Lifetime(r) => { + region_known_to_outlive(tcx, def_id, param_env, &wf_tys, r, outlived_region) + } + ty::GenericArgKind::Type(t) => { + ty_known_to_outlive(tcx, def_id, param_env, &wf_tys, t, outlived_region) + } + ty::GenericArgKind::Const(_) => false, + }) + .collect(); + result.push((outlived_region, outliving_args)); + } + ty::EarlyBinder::bind(tcx, result) +} + +// type Assoc<'a, 'b, 'c: 'a> = (&'a &'c (), &'b ()); + +/// For a param-env clause `for<'v..> ::Assoc<..>: 'bound` that +/// applies to `ty` (an alias with `alias_def_id`), returns the set of (identity) args +/// that the underlying type could possibly capture, as restricted by this clause. +/// +/// As an example, let's imagine we had the following associated type defintion: +/// ```ignore (illustrative) +/// type Assoc<'a, 'b, 'c: 'a> = (&'a &'c (), &'b ()); +/// ``` +/// +/// the following clause: +/// ```ignore (illustrative) +/// for<'x, 'y> T::Assoc<'x, 'x, 'y>: 'x +/// ``` +/// +/// We know from the clause alone that *given some substitution of `T:Assoc`*, +/// we know that it can capture either the first or the second region. However, +/// the bounds on the associated type itself additionally imply that the +/// third region can *also* be captured, because it outlives the first. +/// +/// Now, let's assume we had this clause: +/// ```ignore (illustrative) +/// for<'x, 'y> T::Assoc<'x, 'y, 'x>: 'x +/// ``` +/// +/// Here, we know that `'a` and `'c` could be captured, but there is no outlives +/// relationship to `'b` for either of those, so the underlying type can't +/// capture any arg containing `'b`. +/// +/// Note: because higher-ranked bounds don't have implications, there will be +/// some cases (like `for<'x, 'y, 'z> T::Assoc<'x, 'y, 'z>: 'x`) that won't +/// be satisfiable today, but the logic here should hold whenever there *is*. +/// +/// Returns `None` if the clause doesn't apply to `ty` or gives us no information. +#[tracing::instrument(level = "debug", skip(tcx), ret)] +fn live_args_for_clause<'tcx>( + tcx: TyCtxt<'tcx>, + alias_def_id: DefId, + ty: Ty<'tcx>, + clause: ty::Clause<'tcx>, +) -> Option>> { + let outlives = clause.as_type_outlives_clause()?; + let ty::Alias(_, ty::AliasTy { kind: clause_alias_kind, args: clause_args, .. }) = + *outlives.skip_binder().0.kind() + else { + return None; + }; + let clause_def_id = match clause_alias_kind { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + if clause_def_id != alias_def_id { + return None; + } + + // Here, we're just using this to check if the clause *could apply* to `ty`, + // but importantly we don't want to use the returned region, because that is + // the "last visited" region in `ty` that matches the outlves bound. Actually, + // we want *all* the identity regions in `ty` that match the outlives bound. + test_type_match::extract_verify_if_eq( + tcx, + &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }), + ty, + )?; + + let bound_region = outlives.skip_binder().1; + let clause_identity_args = ty::GenericArgs::identity_for_item(tcx, alias_def_id); + match bound_region.kind() { + // The underlying type must outlive `'static`, so it can't capture any of the args at all. + ty::ReStatic => Some(FxIndexSet::default()), + ty::ReBound(_, br) => { + // The bound is one of the clause's higher-ranked vars. Find the arg + // positions it occupies, then (at the alias's identity level) find + // all args that are known to outlive one of those positions given + // the alias's declared bounds -- only those can be captured by the + // underlying type. + let mut bound_regions = Vec::new(); + for (clause_arg, identity_arg) in clause_args.iter().zip(clause_identity_args.iter()) { + match clause_arg.kind() { + ty::GenericArgKind::Lifetime(r) => { + if let ty::ReBound(_, arg_br) = r.kind() + && arg_br.var == br.var + { + bound_regions.push(identity_arg.expect_region()); + } + } + ty::GenericArgKind::Type(_) | ty::GenericArgKind::Const(_) => { + // A bound var inside a type or const arg (e.g. + // `for<'a> >::Output: 'a`) + // can't be reasoned about at the identity-param level, + // so conservatively treat the clause as giving no + // restriction at all. + if clause_arg.has_escaping_bound_vars() { + return None; + } + } + } + } + if bound_regions.is_empty() { + // The bound var doesn't appear in the args at all, so the clause + // requires the underlying type to outlive *every* region, which + // is equivalent to a `'static` bound. + return Some(FxIndexSet::default()); + } + + // The underlying type can capture any arg that's known to outlive one + // of the bound var's positions (they're all instantiated to the same + // region at any use site this clause applies to). + let args_known_to_outlive = tcx.args_known_to_outlive_alias_params(alias_def_id); + tracing::debug!(?bound_regions, ?args_known_to_outlive); + let mut capturable_args = FxIndexSet::default(); + for &bound_region in &bound_regions { + let (_, outliving_args) = args_known_to_outlive + .as_ref() + .skip_binder() + .iter() + .find(|(region, _)| *region == bound_region) + .unwrap(); + capturable_args.extend(outliving_args.iter().copied()); + } + Some(capturable_args) + } + // A free region (e.g. `for<'a> T::Assoc<'a, 'b>: 'b`): no higher-ranked + // arg of the alias can be proven (by a caller) to outlive a free region + // today, and pinned args only ever match universal regions (which are + // always live), so we conservatively treat this as giving no + // restriction. + // + // NB: if we ever get implied bounds inside binders, a bound var *could* + // be assumed to outlive a free region (e.g. `for<'a> T::Assoc<'x, 'a>: 'x` + // with a declared `'b: 'a` on the assoc type implies `'a: 'x` under the + // binder), so this would need revisiting -- though the result would + // still only need to include bound-var positions. + _ => None, + } } /// Visits free regions in the type that are relevant for liveness computation. /// These regions are passed to `OP`. /// /// Specifically, we visit all of the regions of types recursively, except if -/// the type is an alias, we look at the outlives bounds in the param-env -/// and alias's item bounds. If there is a unique outlives bound, then visit -/// that instead. If there is not a unique but there is a `'static` outlives -/// bound, then don't visit anything. Otherwise, walk through the opaque's -/// regions structurally. +/// the type is an alias, we look at the outlives bounds in the param-env and +/// the alias's item bounds. Each such bound restricts which of the alias's +/// args the underlying type could have captured, so only those (capturable) +/// args are visited. If there are no applicable bounds, we walk through the +/// alias's (non-bivariant) args structurally. pub struct FreeRegionsVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> { pub tcx: TyCtxt<'tcx>, pub param_env: ty::ParamEnv<'tcx>, @@ -216,22 +530,19 @@ where // either `'static` or a unique outlives region, and if one is // found, we just need to prove that that region is still live. // If one is not found, then we continue to walk through the alias. - ty::Alias(_, alias_ty @ ty::AliasTy { kind, args, .. }) => { + ty::Alias(_, ty::AliasTy { kind, args, .. }) => { let tcx = self.tcx; let param_env = self.param_env; - // Opaques are special, because there are additional captured regions that we need to consider. + // Opaques are special, because we need to map onto the opaque's parent. if let ty::AliasTyKind::Opaque { def_id } = kind { - let opaque_outlives_args = - tcx.live_regions_for_opaque_from_outlives_bounds(def_id); - - match opaque_outlives_args { - Some(opaque_live_regions) => { - for r in opaque_live_regions.as_ref().skip_binder() { - let r = ty::EarlyBinder::bind(tcx, *r) + match tcx.live_args_for_alias_from_outlives_bounds(def_id) { + Some(live_args) => { + for &arg in live_args.as_ref().skip_binder() { + let arg = ty::EarlyBinder::bind(tcx, arg) .instantiate(tcx, args) .skip_norm_wip(); - r.visit_with(self); + arg.visit_with(self); } } None => { @@ -247,58 +558,56 @@ where return; } + // For aliases other than opaques, we have to consider two + // sources of information to identity potentially-live args: + // - Bounds on alias item itself + // - Outlives clauses on the current function that apply to the alias + // + // Each source of information *restricts* the set of potentially-live + // args independently: only the args that can be live for *every* + // source of information can be actually live, so we take the intersection. let def_id = match kind { ty::AliasTyKind::Projection { def_id } | ty::AliasTyKind::Inherent { def_id } | ty::AliasTyKind::Opaque { def_id } | ty::AliasTyKind::Free { def_id } => def_id, }; - let outlives_bounds: Vec<_> = tcx - .item_bounds(def_id) - .iter_instantiated(tcx, args) - .map(Unnormalized::skip_norm_wip) - .chain(param_env.caller_bounds()) - .filter_map(|clause| { - let outlives = clause.as_type_outlives_clause()?; - if let Some(outlives) = outlives.no_bound_vars() - && outlives.0 == ty - { - Some(outlives.1) - } else { - test_type_match::extract_verify_if_eq( - tcx, - &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { - VerifyIfEq { ty, bound } - }), - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. - alias_ty.to_ty(tcx, ty::IsRigid::No), - ) + let mut capturable: Option>> = None; + let mut restrict = |capturable_args: FxIndexSet>| { + capturable = Some(match capturable.take() { + None => capturable_args, + Some(prev) => prev.intersection(&capturable_args).copied().collect(), + }); + }; + + if let Some(live_args) = tcx.live_args_for_alias_from_outlives_bounds(def_id) { + restrict(live_args.as_ref().skip_binder().iter().copied().collect()); + } + + for clause in param_env.caller_bounds() { + if let Some(capturable_args) = live_args_for_clause(tcx, def_id, ty, clause) { + restrict(capturable_args); + } + } + tracing::debug!(?capturable); + + match capturable { + Some(capturable_args) => { + for arg in capturable_args { + let arg = ty::EarlyBinder::bind(tcx, arg) + .instantiate(tcx, args) + .skip_norm_wip(); + arg.visit_with(self); } - }) - .collect(); - tracing::debug!(?outlives_bounds); - // If we find `'static`, then we know the alias doesn't capture *any* regions. - // Otherwise, all of the outlives regions should be equal -- if they're not, - // we don't really know how to proceed, so we continue recursing through the - // alias. - if outlives_bounds.contains(&tcx.lifetimes.re_static) { - // no - } else if let Some(r) = outlives_bounds.first() - && outlives_bounds[1..].iter().all(|other_r| other_r == r) - { - assert!(r.type_flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS)); - r.visit_with(self); - } else { - // Skip lifetime parameters that are not captured, since they do - // not need to be live. - let variances = tcx.opt_alias_variances(kind); - - for (idx, s) in args.iter().enumerate() { - if variances.map(|variances| variances[idx]) != Some(ty::Bivariant) { - s.visit_with(self); + } + None => { + // Skip lifetime parameters that are not captured, since they do + // not need to be live. + let variances = tcx.opt_alias_variances(kind); + for (idx, s) in args.iter().enumerate() { + if variances.map(|variances| variances[idx]) != Some(ty::Bivariant) { + s.visit_with(self); + } } } } diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr index 59d7bf0b938d5..3c31169a8675d 100644 --- a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/gat-hide-lifetime-for-swap.rs:35:5 + --> $DIR/gat-hide-lifetime-for-swap.rs:33:5 | LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr index 59d7bf0b938d5..3c31169a8675d 100644 --- a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/gat-hide-lifetime-for-swap.rs:35:5 + --> $DIR/gat-hide-lifetime-for-swap.rs:33:5 | LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..3c31169a8675d --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-for-swap.rs:33:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs index bbb9f9aee6c45..9cbaa5102f015 100644 --- a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs @@ -4,8 +4,6 @@ //@ [edition2024] edition: 2024 //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass // Test to show what happens if we were not careful and allowed invariant // lifetimes to escape through a GAT. @@ -32,7 +30,7 @@ fn dangle_ref(h: &H) -> &'static [i32; 3] { let mut res = &[4, 5, 6]; let x = [1, 2, 3]; h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); - res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` } struct H; impl Hider for H { diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr index e9703c4302af7..ccc8dfbe5fc88 100644 --- a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:35:5 + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:33:5 | LL | h.hide(&mut res).swap(h.hide(&mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr index e9703c4302af7..ccc8dfbe5fc88 100644 --- a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:35:5 + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:33:5 | LL | h.hide(&mut res).swap(h.hide(&mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..ccc8dfbe5fc88 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | h.hide(&mut res).swap(h.hide(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs index 04b62a460190f..3513bd432a9c6 100644 --- a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs @@ -4,8 +4,6 @@ //@ [edition2024] edition: 2024 //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass // Like gat-hide-lifetime-for-swap, but the invariant lifetime is hidden // inside a *type* argument of the GAT rather than being a region argument. @@ -32,7 +30,7 @@ fn dangle(h: &H) -> &'static [i32; 3] { let mut res = &[4, 5, 6]; let x = [1, 2, 3]; h.hide(&mut res).swap(h.hide(&mut &x)); - res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` } struct H; impl Hider for H { diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr index 40450ab13a262..aa7fc52d7d463 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:35:5 + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:33:5 | LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr index 40450ab13a262..aa7fc52d7d463 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:35:5 + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:33:5 | LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..aa7fc52d7d463 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs index 1381a87071f39..35d51fe791a84 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs @@ -4,8 +4,6 @@ //@ [edition2024] edition: 2024 //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass // Like rpit-hide-lifetime-for-swap, but the invariant lifetime is hidden // inside a *type* argument of the opaque rather than being captured as a @@ -32,7 +30,7 @@ fn dangle() -> &'static [i32; 3] { let mut res = &[4, 5, 6]; let x = [1, 2, 3]; hide(&lock, &mut res).swap(hide(&lock, &mut &x)); - res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` } fn main() { From cb8b1477b496e5c0776122f72d04bf901598b74d Mon Sep 17 00:00:00 2001 From: jackh726 Date: Sat, 13 Jun 2026 20:04:31 +0000 Subject: [PATCH 04/78] Move opaque outlives computation out of live_args_for_alias_from_outlives_bounds for opaques --- .../src/rmeta/decoder/cstore_impl.rs | 1 - compiler/rustc_metadata/src/rmeta/encoder.rs | 3 +- compiler/rustc_middle/src/queries.rs | 5 +- compiler/rustc_middle/src/query/keys.rs | 12 + .../src/traits/outlives_for_liveness.rs | 412 +++++++----------- compiler/rustc_type_ir/src/ty_kind.rs | 2 +- .../ui/borrowck/alias-liveness/nested-rpit.rs | 77 ++++ ...-hide-lifetime-for-swap.edition2015.stderr | 2 +- ...-hide-lifetime-for-swap.edition2024.stderr | 2 +- ...de-lifetime-for-swap.polonius_alpha.stderr | 11 + .../rpitit-hide-lifetime-for-swap.rs | 4 +- 11 files changed, 270 insertions(+), 261 deletions(-) create mode 100644 tests/ui/borrowck/alias-liveness/nested-rpit.rs create mode 100644 tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index e447dace2343d..c5c2182d1f715 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -417,7 +417,6 @@ provide! { tcx, def_id, other, cdata, } anon_const_kind => { table } const_of_item => { table } - live_args_for_alias_from_outlives_bounds => { table } args_known_to_outlive_alias_params => { table } } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 1348c5a67222e..5736f1c3e76ee 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1626,13 +1626,12 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); } - record!(self.tables.live_args_for_alias_from_outlives_bounds[def_id] <- tcx.live_args_for_alias_from_outlives_bounds(def_id)); + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); } if let DefKind::AssocTy = def_kind { let assoc_item = tcx.associated_item(def_id); match assoc_item.container { ty::AssocContainer::Trait => { - record!(self.tables.live_args_for_alias_from_outlives_bounds[def_id] <- tcx.live_args_for_alias_from_outlives_bounds(def_id)); record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); } ty::AssocContainer::InherentImpl => { diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index a4586b79eeffb..5d9530b86af15 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2137,10 +2137,9 @@ rustc_queries! { /// (and so is `T`, since `T: 'static` implies `T: 'a`) /// - `bar` outlives `'static`, so we know that no args are potentially live and we can return an empty set /// - `baz` has no outlives bound, so return `None` and let the caller decide what to do - query live_args_for_alias_from_outlives_bounds(def_id: DefId) -> &'tcx Option>>> { + query live_args_for_alias_from_outlives_bounds(kind: ty::AliasTyKind<'tcx>) -> &'tcx Option>>> { arena_cache - desc { "identifying live args for alias `{}`", tcx.def_path_str(def_id) } - separate_provide_extern + desc { "identifying live args for alias `{:?}`", kind } } /// For each region param of an alias, the identity args that are known to diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index 5eedeb4f9ed2e..772caa0b9f505 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -271,6 +271,18 @@ impl<'tcx> QueryKey for ty::Clauses<'tcx> { } } +impl<'tcx> QueryKey for ty::AliasTyKind<'tcx> { + fn default_span(&self, tcx: TyCtxt<'_>) -> Span { + let def_id = match self { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + tcx.def_span(*def_id) + } +} + impl<'tcx, T: QueryKey> QueryKey for ty::PseudoCanonicalInput<'tcx, T> { fn default_span(&self, tcx: TyCtxt<'_>) -> Span { self.value.default_span(tcx) diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs index 6695b987d3e44..acfcb4d5d2611 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -3,54 +3,48 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::bug; use rustc_middle::ty::{ - self, Flags, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, + self, Flags, ImplTraitInTraitData, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, }; use crate::infer::outlives::test_type_match; use crate::infer::region_constraints::VerifyIfEq; use crate::regions::{region_known_to_outlive, ty_known_to_outlive}; -/// For a given opaque type, this returns the set of (identity) generic args that are relevant -/// for liveness, that can be inferred from outlives bounds on the opaque. Callers should -/// instantiate the returned args with the concrete args of the alias. +/// For a given alias type, this returns the set of (identity) generic args that +/// are relevant for liveness, that can be inferred from outlives bounds on the +/// alias itself, and the explicit and implicit outlives clauses of the alias. +/// Callers should instantiate the returned args with the concrete args of the alias. /// /// There are three cases to consider: /// 1. If there are *no* outlives bounds, then we return None. -/// 2. If there is a `'static` outlives bound, then we know that all args are irrelevant, so we return an empty list. -/// 3. If there are *any* outlives bounds, then we find any args that are known to outlive those -/// bounds -- those are the only args whose regions the underlying type could capture. -/// -/// Note that the returned args can include *type* args: e.g. given a `T: 'a` bound in the -/// defining scope, the underlying type of an opaque with an `'a` outlives bound can capture `T`, -/// and so all the regions of the instantiated type arg must be considered live. +/// 2. If there is a `'static` outlives bound, then we know that all args are +/// irrelevant, so we return an empty list. +/// 3. If there are *any* outlives bounds, then we find any args that are known +/// to outlive those bounds, since those are the args whose regions the +/// underlying type could capture. #[tracing::instrument(level = "debug", skip(tcx), ret)] pub(crate) fn live_args_for_alias_from_outlives_bounds<'tcx>( tcx: TyCtxt<'tcx>, - def_id: LocalDefId, -) -> Option>>> { - match tcx.def_kind(def_id) { - DefKind::AssocTy | DefKind::TyAlias => { - live_args_for_projection_from_outlives_bounds(tcx, def_id) - } - DefKind::OpaqueTy => live_args_for_opaque_from_outlives_bounds(tcx, def_id), - kind => { - bug!("improper def_kind {kind:?} passed to `live_args_for_alias_from_outlives_bounds`") - } - } -} - -#[tracing::instrument(level = "debug", skip(tcx), ret)] -pub(crate) fn live_args_for_opaque_from_outlives_bounds<'tcx>( - tcx: TyCtxt<'tcx>, - def_id: LocalDefId, + kind: ty::AliasTyKind<'tcx>, ) -> Option>>> { + let def_id = match kind { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); - // We first want to collect the outlives bounds of the opaque. + // We first want to collect the outlives bounds of the alias. let bounds = tcx.item_bounds(def_id).instantiate_identity().skip_norm_wip(); tracing::debug!(?bounds); - let alias_ty = Ty::new_opaque(tcx, ty::IsRigid::No, def_id.to_def_id(), self_identity_args); - let opaque_outlives_regions: Vec<_> = bounds + let alias_ty = Ty::new_alias( + tcx, + ty::IsRigid::No, + ty::AliasTy::new_from_args(tcx, kind, self_identity_args), + ); + let outlives_regions: Vec<_> = bounds .iter() .filter_map(|clause| { let outlives = clause.as_type_outlives_clause()?; @@ -72,66 +66,87 @@ pub(crate) fn live_args_for_opaque_from_outlives_bounds<'tcx>( } }) .collect(); - tracing::debug!(?opaque_outlives_regions); + tracing::debug!(?outlives_regions); // If there are no outlives bounds, then all (non-bivariant) args are potentially live. - if opaque_outlives_regions.is_empty() { + if outlives_regions.is_empty() { return None; } - // If any of the outlives bounds are `'static`, then we know the opaque doesn't capture - // *any* regions, so we can skip visiting any regions at all. + // If any of the outlives bounds are `'static`, then we know the alias + // doesn't capture *any* regions, so we can skip visiting any regions at all. // - // Thinking about it, I was originally a bit concerned about something like `'a: 'static`, and - // whether or not we need to mark `'a` as live. I don't think *today* we do, since I think regions - // that outlive `'static` are special enough, but I *could* imagine some world where we need to be - // more careful about this. Given I can't find a test that goes wrong, I'm going to leave in this - // optimization. - if opaque_outlives_regions.contains(&tcx.lifetimes.re_static) { - tracing::debug!("opaque has a 'static outlives bound, so skipping visiting any regions"); + // I was originally a bit concerned about something like `'a: 'static`, and + // whether or not we need to mark `'a` as live. I don't think *today* we do, + // since I think regions that outlive `'static` are special enough, but I + // *could* imagine some world where we need to be more careful about this. + // Given I can't find a test that goes wrong, I'm going to leave in this optimization. + if outlives_regions.contains(&tcx.lifetimes.re_static) { + tracing::debug!("alias has a 'static outlives bound, so skipping visiting any regions"); return Some(ty::EarlyBinder::bind(tcx, vec![])); } // Okay, so we know we have some outlives bounds, and that none of them are `'static`. - // Now, we need to find all other potentially-live regions, - // those that outlive an outlives-bound region and are captured. - // We will map both the opaque outlives regions and the set of captured regions - // back to the parent, and then use all bounds (explicit and implied) to - // find the set of captured regions that outlive the outlives bounds. + // Now, we need to find all other potentially-live args, those that outlive + // an outlives-bound region. `args_known_to_outlive_alias_params` does this + // for us, and in the case of opaques only includes *captured* regions, too. - let opaque_captured_lifetimes = tcx.opaque_captured_lifetimes(def_id); - tracing::debug!(?opaque_captured_lifetimes); - - // Map the outlives regions to the parent regions - let generics = tcx.generics_of(def_id); - let parent_outlives_regions: Vec<_> = opaque_outlives_regions - .iter() - .map(|opaque_region| { - let region_def_id = match opaque_region.kind() { - ty::ReEarlyParam(ebr) => generics.param_at(ebr.index as usize, tcx).def_id, - _ => panic!("unexpected region `{opaque_region}` in opaque bounds"), - }; - let region_param = - generics.own_params.iter().find(|param| param.def_id == region_def_id).unwrap(); - let (_, opaque_region_def_id) = opaque_captured_lifetimes - .iter() - .find(|(_, opaque_r)| opaque_r.to_def_id() == region_def_id) - .unwrap(); - let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_region_def_id); - tracing::debug!(?region_def_id, ?region_param, ?parent_region); - parent_region - }) - .collect(); - tracing::debug!(?parent_outlives_regions); + let args_known_to_outlive = + tcx.args_known_to_outlive_alias_params(def_id).as_ref().skip_binder(); + tracing::debug!(?args_known_to_outlive); + let mut live_args: Option>> = None; + for outlives_region in outlives_regions { + let Some(outlives_regions) = + args_known_to_outlive.iter().find(|(r, _)| *r == outlives_region) + else { + continue; + }; + let new_live_args = outlives_regions.1.iter().copied().collect(); + live_args = Some(match live_args.take() { + None => new_live_args, + Some(prev) => prev.intersection(&new_live_args).copied().collect(), + }); + } + live_args.map(|c| ty::EarlyBinder::bind(tcx, c.into_iter().collect())) +} - // Map the captured regions to the parent regions - let mut parent_captured_regions: Vec<(ty::Region<'tcx>, LocalDefId)> = - Vec::with_capacity(opaque_captured_lifetimes.len()); - for (_, opaque_lt) in opaque_captured_lifetimes.iter() { - let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_lt); - parent_captured_regions.push((parent_region, *opaque_lt)); +/// For each region param of this alias compute the identity args that are known +/// to outlive it, given only the alias's declared where-clauses. +/// +/// Note: for opaques (including synthetic associated types from RPITITs), +/// the outlives relationships are identified in the context of the *parent*, +/// since bounds and well-formed types are not lowered. +// FIXME: this likely should return a `BitSet` instead of a `Vec>` +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn args_known_to_outlive_alias_params<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + match tcx.def_kind(def_id) { + DefKind::OpaqueTy => args_known_to_outlive_opaque_params(tcx, def_id), + DefKind::AssocTy + if let Some(ImplTraitInTraitData::Trait { fn_def_id: _, opaque_def_id }) = + tcx.opt_rpitit_info(def_id.to_def_id()) => + { + args_known_to_outlive_opaque_params(tcx, opaque_def_id.expect_local()) + } + DefKind::AssocTy | DefKind::TyAlias => { + args_known_to_outlive_associated_type_params(tcx, def_id) + } + kind => { + bug!("improper def_kind {kind:?} passed to `live_args_for_alias_from_outlives_bounds`") + } } - tracing::debug!(?parent_captured_regions); +} + +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + + let mut result = Vec::new(); // For implied bounds, we need the set of WF types from the parents. // - For functions, this is all the input and output types. @@ -149,167 +164,93 @@ pub(crate) fn live_args_for_opaque_from_outlives_bounds<'tcx>( } rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => (parent, FxIndexSet::default()), }; - - // It certainly would be nice to use `args_known_to_outlive_alias_params` here, - // but unfortunately we need to check the outlives relationships in the *parent*, - // not the opaque itself. - - // Find all the opaque's captured regions that outlive the outlives bounds using the implied bounds let parent_param_env = tcx.param_env(parent_def_id); tracing::debug!(?parent_param_env); - let mut live_args: Vec> = - Vec::with_capacity(parent_outlives_regions.len()); - for (parent_captured_region, opaque_captured_def_id) in parent_captured_regions.iter() { - let mut all_outlives = true; - for parent_outlives_region in parent_outlives_regions.iter() { - let known_outlives = region_known_to_outlive( - tcx, - parent_def_id.expect_local(), - parent_param_env, - &wf_tys, - *parent_captured_region, - *parent_outlives_region, - ); - tracing::debug!(?parent_captured_region, ?parent_outlives_region, ?known_outlives); - if !known_outlives { - all_outlives = false; - break; - } - } - if all_outlives { - let param = generics - .own_params - .iter() - .find(|param| param.def_id == opaque_captured_def_id.to_def_id()) - .unwrap(); - let opaque_region = - ty::Region::new_early_param(tcx, param.to_early_bound_region_data()); - live_args.push(opaque_region.into()); - } - } - - // The underlying type can also capture the opaque's *type* params (which are inherited from - // the parent), if they are known to outlive the outlives bounds (e.g. from a `T: 'a` bound - // in the parent's environment). In that case, all the regions of the instantiated type arg - // must be considered live. The parent's *region* params don't need to be considered here: - // the captured ones are duplicated as the opaque's own params (handled above), and the - // parent positions are bivariant. - for arg in self_identity_args.iter() { - let Some(param_ty) = arg.as_type() else { + // Map the outlives regions to the parent regions + let generics = tcx.generics_of(def_id); + let opaque_captured_lifetimes = tcx.opaque_captured_lifetimes(def_id); + let mut parent_outlives_regions = Vec::with_capacity(generics.own_params.len()); + for opaque_arg in self_identity_args[generics.parent_count..].iter() { + let Some(opaque_region) = opaque_arg.as_region() else { continue; }; - let all_outlives = parent_outlives_regions.iter().all(|parent_outlives_region| { - let known_outlives = ty_known_to_outlive( - tcx, - parent_def_id.expect_local(), - parent_param_env, - &wf_tys, - param_ty, - *parent_outlives_region, - ); - tracing::debug!(?param_ty, ?parent_outlives_region, ?known_outlives); - known_outlives - }); - if all_outlives { - live_args.push(arg); - } + let region_def_id = match opaque_region.kind() { + ty::ReEarlyParam(ebr) => generics.param_at(ebr.index as usize, tcx).def_id, + _ => panic!("unexpected region `{opaque_region}` in opaque bounds"), + }; + let region_param = + generics.own_params.iter().find(|param| param.def_id == region_def_id).unwrap(); + let (_, opaque_region_def_id) = opaque_captured_lifetimes + .iter() + .find(|(_, opaque_r)| opaque_r.to_def_id() == region_def_id) + .unwrap(); + let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_region_def_id); + tracing::debug!(?region_def_id, ?region_param, ?parent_region); + parent_outlives_regions.push((parent_region, opaque_region)); } - tracing::debug!(?live_args); + tracing::debug!(?parent_outlives_regions); - Some(ty::EarlyBinder::bind(tcx, live_args)) -} + for (parent_outlived_region, opaque_outlived_region) in parent_outlives_regions.iter() { + let mut opaque_outlives_args = Vec::with_capacity(self_identity_args.len()); + for parent_outlives_arg in self_identity_args[..generics.parent_count].iter() { + let type_outlives = match parent_outlives_arg.kind() { + // Consts don't have any non-static regions + ty::GenericArgKind::Const(_) => continue, + // Lifetimes should be captured + ty::GenericArgKind::Lifetime(_) => continue, + ty::GenericArgKind::Type(t) => ty_known_to_outlive( + tcx, + def_id, + parent_param_env, + &wf_tys, + t, + *parent_outlived_region, + ), + }; + if !type_outlives { + continue; + } -#[tracing::instrument(level = "debug", skip(tcx), ret)] -pub(crate) fn live_args_for_projection_from_outlives_bounds<'tcx>( - tcx: TyCtxt<'tcx>, - def_id: LocalDefId, -) -> Option>>> { - let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + // Types aren't captured, so don't need to map to the opaque + opaque_outlives_args.push(*parent_outlives_arg); + } - // We first want to collect the outlives bounds of the opaque. - let bounds = tcx.item_bounds(def_id).instantiate_identity().skip_norm_wip(); - tracing::debug!(?bounds); - let alias_ty = Ty::new_projection(tcx, ty::IsRigid::No, def_id.to_def_id(), self_identity_args); - let alias_outlives_regions: Vec<_> = bounds - .iter() - .filter_map(|clause| { - let outlives = clause.as_type_outlives_clause()?; - if let Some(outlives) = outlives.no_bound_vars() - && outlives.0 == alias_ty - { - Some(outlives.1) - } else { - test_type_match::extract_verify_if_eq( + for (_, opaque_lt) in opaque_captured_lifetimes.iter() { + let parent_outlives_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_lt); + + let region_outlives = parent_outlives_region == *parent_outlived_region + || region_known_to_outlive( tcx, - &outlives - .map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }), - alias_ty, - ) + def_id, + parent_param_env, + &wf_tys, + parent_outlives_region, + *parent_outlived_region, + ); + if !region_outlives { + continue; } - }) - .collect(); - tracing::debug!(?alias_outlives_regions); - // If there are no outlives bounds, then all (non-bivariant) args are potentially live. - if alias_outlives_regions.is_empty() { - return None; - } - - // If any of the outlives bounds are `'static`, then we know the opaque doesn't capture - // *any* regions, so we can skip visiting any regions at all. - // - // Thinking about it, I was originally a bit concerned about something like `'a: 'static`, and - // whether or not we need to mark `'a` as live. I don't think *today* we do, since I think regions - // that outlive `'static` are special enough, but I *could* imagine some world where we need to be - // more careful about this. Given I can't find a test that goes wrong, I'm going to leave in this - // optimization. - if alias_outlives_regions.contains(&tcx.lifetimes.re_static) { - tracing::debug!("opaque has a 'static outlives bound, so skipping visiting any regions"); - return Some(ty::EarlyBinder::bind(tcx, vec![])); - } + let param = generics + .own_params + .iter() + .find(|param| param.def_id == opaque_lt.to_def_id()) + .unwrap(); + let opaque_region = + ty::Region::new_early_param(tcx, param.to_early_bound_region_data()); - tracing::debug!(?alias_outlives_regions); + opaque_outlives_args.push(opaque_region.into()); + } - // Find all the args that are known to outlive the outlives bounds given the assoc type's - // declared where-clauses -- those are the only args the underlying type could capture. Note - // that this includes *type* args (incl. `Self`): e.g. a declared `U: 'a` lets the underlying - // type capture `U`, so all the regions of the instantiated type arg must be considered live. - // Each bound restricts the set independently, so we take the intersection. - let args_known_to_outlive = tcx.args_known_to_outlive_alias_params(def_id.to_def_id()); - tracing::debug!(?args_known_to_outlive); - let mut live_args: Option>> = None; - for outlives_region in alias_outlives_regions { - let (_, outliving_args) = args_known_to_outlive - .as_ref() - .skip_binder() - .iter() - .find(|(region, _)| *region == outlives_region) - .unwrap(); - let outliving_args: FxIndexSet<_> = outliving_args.iter().copied().collect(); - live_args = Some(match live_args.take() { - None => outliving_args, - Some(prev) => prev.intersection(&outliving_args).copied().collect(), - }); + result.push((*opaque_outlived_region, opaque_outlives_args)); } - let live_args: Vec<_> = live_args.unwrap().into_iter().collect(); - tracing::debug!(?live_args); - Some(ty::EarlyBinder::bind(tcx, live_args)) + ty::EarlyBinder::bind(tcx, result) } -/// For each region param of this alias (identified by its index into the alias's generics), -/// compute the identity args that are known to outlive it, given only the alias's declared -/// where-clauses. These are the only args whose regions the underlying type of the alias -/// could capture while satisfying an outlives bound on that param. -/// -/// Note that the outliving args can include *type* args (incl. `Self`): e.g. a declared -/// `U: 'a` bound means `U` is known to outlive `'a`, so the underlying type can capture `U`. -/// Const args are never included: const param types can't contain (non-`'static`) regions, -/// so they're irrelevant for liveness. -// FIXME: this likely should return a `BitSet` instead of a `Vec>` #[tracing::instrument(level = "debug", skip(tcx), ret)] -pub(crate) fn args_known_to_outlive_alias_params<'tcx>( +pub(crate) fn args_known_to_outlive_associated_type_params<'tcx>( tcx: TyCtxt<'tcx>, def_id: LocalDefId, ) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { @@ -339,13 +280,11 @@ pub(crate) fn args_known_to_outlive_alias_params<'tcx>( ty::EarlyBinder::bind(tcx, result) } -// type Assoc<'a, 'b, 'c: 'a> = (&'a &'c (), &'b ()); - /// For a param-env clause `for<'v..> ::Assoc<..>: 'bound` that /// applies to `ty` (an alias with `alias_def_id`), returns the set of (identity) args /// that the underlying type could possibly capture, as restricted by this clause. /// -/// As an example, let's imagine we had the following associated type defintion: +/// As an example, let's imagine we had the following associated type definition: /// ```ignore (illustrative) /// type Assoc<'a, 'b, 'c: 'a> = (&'a &'c (), &'b ()); /// ``` @@ -522,7 +461,6 @@ where match *ty.kind() { // We can prove that an alias is live two ways: // 1. All the components are live. - // // 2. There is a known outlives bound or where-clause, and that // region is live. // @@ -534,30 +472,6 @@ where let tcx = self.tcx; let param_env = self.param_env; - // Opaques are special, because we need to map onto the opaque's parent. - if let ty::AliasTyKind::Opaque { def_id } = kind { - match tcx.live_args_for_alias_from_outlives_bounds(def_id) { - Some(live_args) => { - for &arg in live_args.as_ref().skip_binder() { - let arg = ty::EarlyBinder::bind(tcx, arg) - .instantiate(tcx, args) - .skip_norm_wip(); - arg.visit_with(self); - } - } - None => { - let variances = tcx.variances_of(def_id); - for (idx, s) in args.iter().enumerate() { - if variances[idx] != ty::Bivariant { - s.visit_with(self); - } - } - } - } - - return; - } - // For aliases other than opaques, we have to consider two // sources of information to identity potentially-live args: // - Bounds on alias item itself @@ -580,7 +494,7 @@ where }); }; - if let Some(live_args) = tcx.live_args_for_alias_from_outlives_bounds(def_id) { + if let Some(live_args) = tcx.live_args_for_alias_from_outlives_bounds(kind) { restrict(live_args.as_ref().skip_binder().iter().copied().collect()); } diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 89fac9d200214..66e76d6b9d0e2 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -27,7 +27,7 @@ use crate::{ mod closure; -#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)] +#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", diff --git a/tests/ui/borrowck/alias-liveness/nested-rpit.rs b/tests/ui/borrowck/alias-liveness/nested-rpit.rs new file mode 100644 index 0000000000000..f927fe8c5d12a --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/nested-rpit.rs @@ -0,0 +1,77 @@ +//@ edition: 2024 +//@ compile-flags: --crate-type lib +//@ check-pass + +struct ScopeGuard +where + F: FnMut(&mut T), +{ + dropfn: F, + value: T, +} + +impl core::ops::Deref for ScopeGuard +where + F: FnMut(&mut T), +{ + type Target = T; + #[inline] + fn deref(&self) -> &T { + &self.value + } +} + +impl core::ops::DerefMut for ScopeGuard +where + F: FnMut(&mut T), +{ + #[inline] + fn deref_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl Drop for ScopeGuard +where + F: FnMut(&mut T), +{ + #[inline] + fn drop(&mut self) { + (self.dropfn)(&mut self.value); + } +} + +struct RawTableInner; + +impl RawTableInner { + fn prepare_resize<'a, A>( + &self, + alloc: &'a A, + ) -> Result, ()> + { + Ok(ScopeGuard { + value: RawTableInner, + dropfn: move |self_| {}, + }) + } + + fn resize_inner( + &mut self, + alloc: &A, + hasher: &dyn Fn(&mut Self, usize) -> u64, + ) -> Result<(), ()> + { + let mut new_table = self.prepare_resize(alloc)?; + + for _ in 0..10 { + hasher(self, 0); + new_table.prepare_insert_index(); + } + + core::mem::swap(self, &mut new_table); + + Ok(()) + } + + fn prepare_insert_index(&mut self) {} +} diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr index f68ebd1610a13..0c2e1d3c7817a 100644 --- a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/rpitit-hide-lifetime-for-swap.rs:33:5 + --> $DIR/rpitit-hide-lifetime-for-swap.rs:31:5 | LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr index f68ebd1610a13..0c2e1d3c7817a 100644 --- a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/rpitit-hide-lifetime-for-swap.rs:33:5 + --> $DIR/rpitit-hide-lifetime-for-swap.rs:31:5 | LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); | -- `x` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..0c2e1d3c7817a --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpitit-hide-lifetime-for-swap.rs:31:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs index fcf949a3e945e..ee20d00ae77cb 100644 --- a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs @@ -2,8 +2,6 @@ //@ ignore-compare-mode-polonius (explicit revisions) //@ [edition2015] edition: 2015 //@ [edition2024] edition: 2024 -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next @@ -30,7 +28,7 @@ fn dangle_ref(h: &H) -> &'static [i32; 3] { let mut res = &[4, 5, 6]; let x = [1, 2, 3]; h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); - res //[edition2015,edition2024]~ ERROR cannot return value referencing local variable `x` + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` } struct H; impl Hider for H {} From 84f289674c391d8a049373a3c1ea46759be2cdaf Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 6 Jun 2026 22:19:12 -0700 Subject: [PATCH 05/78] rustdoc: do not include extra stuff in span This change prevents our lints from returning a span with stuff in it that isn't actually part of the doc comment. When that happens, it returns `None` instead. --- compiler/rustc_resolve/src/rustdoc.rs | 67 ++++++++++++------- .../lints/invalid-html-tags-ice-146890.rs | 8 +-- .../lints/invalid-html-tags-ice-146890.stderr | 2 - tests/rustdoc-ui/lints/invalid-html-tags.rs | 2 +- .../rustdoc-ui/lints/invalid-html-tags.stderr | 1 - .../lints/redundant_explicit_links_split.rs | 43 ++++++++++++ .../redundant_explicit_links_split.stderr | 45 +++++++++++++ 7 files changed, 137 insertions(+), 31 deletions(-) create mode 100644 tests/rustdoc-ui/lints/redundant_explicit_links_split.rs create mode 100644 tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 92bc577f59201..fdfe5afb7d7ec 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::mem; use std::ops::Range; @@ -641,41 +642,61 @@ pub fn source_span_for_markdown_range_inner( let mut start_bytes = 0; let mut end_bytes = 0; + let span = span_of_fragments(fragments)?; + + let mut line_bytes = 0; + let mut sorted_fragments = fragments.to_vec(); + sorted_fragments.sort_by_key(|fragment| fragment.span.lo().0); 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; - match source_line.find(md_line) { - Some(offset) => { - if line_no == starting_line { - start_bytes += offset; + // Since we're counting bytes, `source_line_len` includes the "\n". + let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); + let has_fragment = sorted_fragments + .binary_search_by(|fragment| { + if fragment.span.hi().0 < span.lo().0 + line_bytes { + Ordering::Less + } else if fragment.span.lo().0 > span.lo().0 + line_bytes + source_line_len { + Ordering::Greater + } else { + Ordering::Equal + } + }) + .is_ok(); + line_bytes += source_line_len; + if has_fragment && let Some(offset) = source_line.find(md_line) { + if line_no == starting_line { + start_bytes += offset; - if starting_line == ending_line { - break 'outer; - } - } else if line_no == ending_line { - end_bytes += offset; + if starting_line == ending_line { break 'outer; - } else if line_no < starting_line { - start_bytes += source_line.len() - md_line.len(); - } else { - end_bytes += source_line.len() - md_line.len(); } - break; + } else if line_no == ending_line { + end_bytes += offset; + break 'outer; + } else if line_no < starting_line { + start_bytes += source_line.len() - md_line.len(); + } else { + end_bytes += source_line.len() - md_line.len(); } - None => { - // Since this is a source line that doesn't include a markdown line, - // we have to count the newline that we split from earlier. - if line_no <= starting_line { - start_bytes += source_line.len() + 1; - } else { - end_bytes += source_line.len() + 1; - } + break; + } else { + // Since this is a source line that doesn't include a markdown line, + // we have to count the newline that we split from earlier. + if line_no <= starting_line { + start_bytes += source_line.len() + 1; + } else if source_line.chars().any(|c| !c.is_whitespace()) { + // If we're past the first line, but haven't found the last line, + // we can only return a contiguous span if every line is either + // part of the doc comment or blank. + return None; + } else { + end_bytes += source_line.len() + 1; } } } } - let span = span_of_fragments(fragments)?; let src_span = span.from_inner(InnerSpan::new( md_range.start + start_bytes, md_range.end + start_bytes + end_bytes, diff --git a/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs b/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs index d7efc201e7e39..53df3e9eb5a80 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs +++ b/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs @@ -7,17 +7,17 @@ /// /// key +//~^^ ERROR: unclosed HTML tag `TH` /// +//~^^ ERROR: unopened HTML tag `TD` /// value +//~^^ ERROR: unclosed HTML tag `TH` /// +//~^^ ERROR: unopened HTML tag `TD` /// /// | |_____^ | @@ -18,7 +17,6 @@ error: unopened HTML tag `TD` | LL | /// | |_____^ diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.rs b/tests/rustdoc-ui/lints/invalid-html-tags.rs index 8003e5efdd582..f5700e5846e51 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.rs +++ b/tests/rustdoc-ui/lints/invalid-html-tags.rs @@ -164,8 +164,8 @@ pub fn r() {} /// >
/// > href="#broken" +//~^^ ERROR incomplete HTML tag `img` pub fn s() {} ///
diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.stderr b/tests/rustdoc-ui/lints/invalid-html-tags.stderr index b6ec22c247901..3256004ddb6ab 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.stderr +++ b/tests/rustdoc-ui/lints/invalid-html-tags.stderr @@ -123,7 +123,6 @@ error: incomplete HTML tag `img` | LL | /// > href="#broken" | |____________________^ diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs new file mode 100644 index 0000000000000..9de8bbe3b8c98 --- /dev/null +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs @@ -0,0 +1,43 @@ +#![deny(rustdoc::redundant_explicit_links)] + +// Right now, redundant_explicit_links won't produce a warning at all if +// rustdoc isn't able to calculate an accurate span for the link. +// +// If that changes and this test starts to fail, you should add the +// appropriate annotations, and, also, make sure that it doesn't +// suggest a correction that wipes out the `fn` formal signature +// or the `#[inline]` attribute. + +/// [std::clone::Clone]( +pub fn split_outer_inner() { + //! std::clone::Clone) +} + +/// [std::clone::Clone](std::clone::Clone +pub fn split_outer_inner_b() { + //! ) +} + +/// [std::clone::Clone]( +#[inline] +/// std::clone::Clone) +pub fn split_attr() { +} + +/// [std::clone::Clone](std::clone::Clone +#[inline] +/// ) +pub fn split_attr_b() { +} + +/// [std::clone::Clone]( +/// std::clone::Clone) +//~^^ ERROR redundant_explicit_links +pub fn not_split() { +} + +/// [std::clone::Clone](std::clone::Clone +/// ) +//~^^ ERROR redundant_explicit_links +pub fn not_split_b() { +} diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr new file mode 100644 index 0000000000000..3e2e99f10d9f7 --- /dev/null +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr @@ -0,0 +1,45 @@ +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:33:25 + | +LL | /// [std::clone::Clone]( + | ______-----------------__^ + | | | + | | because label contains path that resolves to same destination +LL | | /// std::clone::Clone) + | |_____________________^ explicit target is redundant + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +note: the lint level is defined here + --> $DIR/redundant_explicit_links_split.rs:1:9 + | +LL | #![deny(rustdoc::redundant_explicit_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: remove explicit link target + | +LL - /// [std::clone::Clone]( +LL - /// std::clone::Clone) +LL + /// [std::clone::Clone] + | + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:39:25 + | +LL | /// [std::clone::Clone](std::clone::Clone + | ______-----------------__^ + | | | + | | because label contains path that resolves to same destination +LL | | /// ) + | |____^ explicit target is redundant + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +help: remove explicit link target + | +LL - /// [std::clone::Clone](std::clone::Clone +LL - /// ) +LL + /// [std::clone::Clone] + | + +error: aborting due to 2 previous errors + From 2001b88a962332f3799d3719867812900561d5bb Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 7 Jun 2026 17:41:40 -0700 Subject: [PATCH 06/78] Fix `unbalanced_ticks` when doc isn't contiguous --- src/tools/clippy/clippy_lints/src/doc/mod.rs | 13 +++---------- .../clippy/tests/ui/doc/unbalanced_ticks.stderr | 5 ++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index c5816cb8b2d63..013fb32bdaf3c 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -1242,7 +1242,8 @@ fn check_doc<'a, Events: Iterator, Range, Range tests/ui/doc/unbalanced_ticks.rs:6:5 + --> tests/ui/doc/unbalanced_ticks.rs:6:1 | -LL | /// This is a doc comment with `unbalanced_tick marks and several words that - | _____^ +LL | / /// This is a doc comment with `unbalanced_tick marks and several words that LL | | LL | | /// should be `encompassed_by` tick marks because they `contain_underscores`. LL | | /// Because of the initial `unbalanced_tick` pair, the error message is From e66a50496cf5e996bb354f61508db540d62eb51a Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 7 Jun 2026 17:46:08 -0700 Subject: [PATCH 07/78] Add test case for split missing punctuation --- ...doc_paragraph_missing_punctuation_split_16169.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs diff --git a/src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs b/src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs new file mode 100644 index 0000000000000..c01b0c90b120e --- /dev/null +++ b/src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs @@ -0,0 +1,13 @@ +//@ check-pass +// https://github.com/rust-lang/rust-clippy/issues/16169 +#![allow(clippy::mixed_attributes_style)] + +/// +pub fn dont_warn_inner_outer() { + //!w +} +/// +#[inline] +///w +pub fn dont_warn_split_by_attr() { +} From f7302b9723082a9310e901b58eae530363571d96 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 13 Jun 2026 09:47:29 -0700 Subject: [PATCH 08/78] Skip the unhelpful binary search It requires an allocation, and doesn't seem to help in practice. Fixes a nit found during review. --- compiler/rustc_resolve/src/rustdoc.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index fdfe5afb7d7ec..8efad0a343ac5 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use std::mem; use std::ops::Range; @@ -645,24 +644,15 @@ pub fn source_span_for_markdown_range_inner( let span = span_of_fragments(fragments)?; let mut line_bytes = 0; - let mut sorted_fragments = fragments.to_vec(); - sorted_fragments.sort_by_key(|fragment| fragment.span.lo().0); 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; // Since we're counting bytes, `source_line_len` includes the "\n". let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); - let has_fragment = sorted_fragments - .binary_search_by(|fragment| { - if fragment.span.hi().0 < span.lo().0 + line_bytes { - Ordering::Less - } else if fragment.span.lo().0 > span.lo().0 + line_bytes + source_line_len { - Ordering::Greater - } else { - Ordering::Equal - } - }) - .is_ok(); + let has_fragment = fragments.iter().any(|fragment| { + fragment.span.hi().0 >= span.lo().0 + line_bytes + && fragment.span.lo().0 <= span.lo().0 + line_bytes + source_line_len + }); line_bytes += source_line_len; if has_fragment && let Some(offset) = source_line.find(md_line) { if line_no == starting_line { From b66a23fb4b2f9cff20ea6f8b34d223e4073a99d8 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 17:58:10 -0700 Subject: [PATCH 09/78] Rename `span` and `line_bytes` to more specific Co-authored-by: binarycat --- compiler/rustc_resolve/src/rustdoc.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 8efad0a343ac5..9abb79d5ca02f 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -641,19 +641,20 @@ pub fn source_span_for_markdown_range_inner( let mut start_bytes = 0; let mut end_bytes = 0; - let span = span_of_fragments(fragments)?; + let span_of_all_fragments = span_of_fragments(fragments)?; - let mut line_bytes = 0; + let mut prev_lines_bytes = 0; 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; // Since we're counting bytes, `source_line_len` includes the "\n". let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); let has_fragment = fragments.iter().any(|fragment| { - fragment.span.hi().0 >= span.lo().0 + line_bytes - && fragment.span.lo().0 <= span.lo().0 + line_bytes + source_line_len + fragment.span.hi().0 >= span_of_all_fragments.lo().0 + prev_lines_bytes + && fragment.span.lo().0 + <= span_of_all_fragments.lo().0 + prev_lines_bytes + source_line_len }); - line_bytes += source_line_len; + prev_lines_bytes += source_line_len; if has_fragment && let Some(offset) = source_line.find(md_line) { if line_no == starting_line { start_bytes += offset; @@ -687,7 +688,7 @@ pub fn source_span_for_markdown_range_inner( } } - let src_span = span.from_inner(InnerSpan::new( + let src_span = span_of_all_fragments.from_inner(InnerSpan::new( md_range.start + start_bytes, md_range.end + start_bytes + end_bytes, )); From f93f2e446b457c871510340c22a4a68192ae48d1 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 18:07:20 -0700 Subject: [PATCH 10/78] Use an intermediate variable for readability Co-authored-by: binarycat --- compiler/rustc_resolve/src/rustdoc.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 9abb79d5ca02f..4cae23ed7e12a 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -641,20 +641,20 @@ pub fn source_span_for_markdown_range_inner( let mut start_bytes = 0; let mut end_bytes = 0; - let span_of_all_fragments = span_of_fragments(fragments)?; + let span_of_all_fragments: Span = span_of_fragments(fragments)?; let mut prev_lines_bytes = 0; 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; - // Since we're counting bytes, `source_line_len` includes the "\n". - let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); - let has_fragment = fragments.iter().any(|fragment| { - fragment.span.hi().0 >= span_of_all_fragments.lo().0 + prev_lines_bytes - && fragment.span.lo().0 - <= span_of_all_fragments.lo().0 + prev_lines_bytes + source_line_len - }); - prev_lines_bytes += source_line_len; + let source_line_len = u32::try_from(source_line.len()).unwrap(); + let source_line_span = + span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0; + let has_fragment = fragments + .iter() + .any(|fragment| fragment.span.contains(source_line_span.shrink_to_hi())); + // Since we're counting bytes, `prev_line_bytes` includes the "\n". + prev_lines_bytes += source_line_len + 1; if has_fragment && let Some(offset) = source_line.find(md_line) { if line_no == starting_line { start_bytes += offset; From 7975dc779706cb384101867ffa9008816ed01d7e Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 19:19:02 -0700 Subject: [PATCH 11/78] Add clarifying comment Co-authored-by: binarycat --- compiler/rustc_resolve/src/rustdoc.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 4cae23ed7e12a..42a5005552825 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -673,13 +673,14 @@ pub fn source_span_for_markdown_range_inner( break; } else { // Since this is a source line that doesn't include a markdown line, - // we have to count the newline that we split from earlier. + // we have to count it and its newline as non-markdown bytes. if line_no <= starting_line { start_bytes += source_line.len() + 1; } else if source_line.chars().any(|c| !c.is_whitespace()) { - // If we're past the first line, but haven't found the last line, - // we can only return a contiguous span if every line is either - // part of the doc comment or blank. + // We're past the first line, but haven't found the last line, + // but we found a non-empty non-markdown line. + // This could be an attribute, and we don't want a diagnostic + // suggesting to delete that attribute, so we return None to be safe. return None; } else { end_bytes += source_line.len() + 1; From 4d83004499d40452cef97b9cd103f53cb98fccd2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 19:34:27 -0700 Subject: [PATCH 12/78] Remove unused `Option` return --- src/librustdoc/passes/lint/redundant_explicit_links.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 8da21f100c6a3..8ccf819fff987 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -80,7 +80,7 @@ fn check_redundant_explicit_link<'md>( hir_id: HirId, doc: &'md str, resolutions: &DocLinkResMap, -) -> Option<()> { +) { let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); let mut offset_iter = Parser::new_with_broken_link_callback( doc, @@ -146,8 +146,6 @@ fn check_redundant_explicit_link<'md>( } } } - - None } /// FIXME(ChAoSUnItY): Too many arguments. From 9438aac0c297bbc8804c0e6796c24637df1f6e99 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 20:15:28 -0700 Subject: [PATCH 13/78] Handle span failure in redundant_explicit_links --- .../passes/lint/redundant_explicit_links.rs | 182 +++++++++++++----- .../lints/redundant_explicit_links_split.rs | 27 ++- .../redundant_explicit_links_split.stderr | 74 +++++-- 3 files changed, 205 insertions(+), 78 deletions(-) diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 8ccf819fff987..ad45b6e1d1c5b 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -115,7 +115,7 @@ fn check_redundant_explicit_link<'md>( } if dest_url.ends_with(resolvable_link) || resolvable_link.ends_with(&*dest_url) { - match link_type { + let check_result = match link_type { LinkType::Inline | LinkType::ReferenceUnknown => { check_inline_or_reference_unknown_redundancy( cx, @@ -127,27 +127,54 @@ fn check_redundant_explicit_link<'md>( dest_url.to_string(), link_data, if link_type == LinkType::Inline { (b'(', b')') } else { (b'[', b']') }, - ); + ) } - LinkType::Reference => { - check_reference_redundancy( - cx, - item, - hir_id, - doc, - resolutions, - link_range, - &dest_url, - link_data, - ); - } - _ => {} + LinkType::Reference => check_reference_redundancy( + cx, + item, + hir_id, + doc, + resolutions, + link_range, + &dest_url, + link_data, + ), + _ => Ok(()), + }; + if let Err(lint) = check_result { + cx.tcx.emit_node_span_lint( + crate::lint::REDUNDANT_EXPLICIT_LINKS, + hir_id, + item.attr_span(cx.tcx), + lint, + ); } } } } } +struct RedundantExplicitLinksCannotSuggest { + attr_span: Span, + display_link: String, + dest_link: String, +} + +impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksCannotSuggest { + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { + let Self { attr_span, display_link, dest_link } = self; + + Diag::new(dcx, level, "redundant explicit link target") + .with_span_label( + attr_span, + format!("explicit target `{dest_link}` is redundant because label `{display_link}` resolves to same destination") + ) + .with_note( + "when a link's destination is not specified,\nthe label is used to resolve intra-doc links" + ) + } +} + /// FIXME(ChAoSUnItY): Too many arguments. fn check_inline_or_reference_unknown_redundancy( cx: &DocContext<'_>, @@ -159,7 +186,7 @@ fn check_inline_or_reference_unknown_redundancy( dest: String, link_data: LinkData, (open, close): (u8, u8), -) -> Option<()> { +) -> Result<(), RedundantExplicitLinksCannotSuggest> { struct RedundantExplicitLinks { explicit_span: Span, display_span: Span, @@ -194,42 +221,65 @@ fn check_inline_or_reference_unknown_redundancy( } } - let (resolvable_link, resolvable_link_range) = - (&link_data.resolvable_link?, &link_data.resolvable_link_range?); - let (dest_res, display_res) = - (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?); + let (Some(resolvable_link), Some(resolvable_link_range)) = + (&link_data.resolvable_link, &link_data.resolvable_link_range) + else { + return Ok(()); + }; + let (Some(dest_res), Some(display_res)) = + (find_resolution(resolutions, &dest), find_resolution(resolutions, resolvable_link)) + else { + return Ok(()); + }; if dest_res == display_res { + let attr_span = item.attr_span(cx.tcx); let link_span = match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings) { Some((sp, from_expansion)) => { if from_expansion { - return None; + return Ok(()); } sp } - None => item.attr_span(cx.tcx), + None => attr_span, }; - let (explicit_span, false) = source_span_for_markdown_range( + let explicit_span = match source_span_for_markdown_range( cx.tcx, doc, &offset_explicit_range(doc, link_range, open, close), &item.attrs.doc_strings, - )? - else { + ) { + Some((explicit_span, false)) => explicit_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; - let (display_span, false) = source_span_for_markdown_range( + let display_span = match source_span_for_markdown_range( cx.tcx, doc, resolvable_link_range, &item.attrs.doc_strings, - )? - else { + ) { + Some((display_span, false)) => display_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; cx.tcx.emit_node_span_lint( @@ -245,7 +295,7 @@ fn check_inline_or_reference_unknown_redundancy( ); } - None + Ok(()) } /// FIXME(ChAoSUnItY): Too many arguments. @@ -258,7 +308,7 @@ fn check_reference_redundancy( link_range: Range, dest: &CowStr<'_>, link_data: LinkData, -) -> Option<()> { +) -> Result<(), RedundantExplicitLinksCannotSuggest> { struct RedundantExplicitLinkTarget { explicit_span: Span, display_span: Span, @@ -292,49 +342,83 @@ fn check_reference_redundancy( } } - let (resolvable_link, resolvable_link_range) = - (&link_data.resolvable_link?, &link_data.resolvable_link_range?); - let (dest_res, display_res) = - (find_resolution(resolutions, dest)?, find_resolution(resolutions, resolvable_link)?); + let (Some(resolvable_link), Some(resolvable_link_range)) = + (&link_data.resolvable_link, &link_data.resolvable_link_range) + else { + return Ok(()); + }; + let (Some(dest_res), Some(display_res)) = + (find_resolution(resolutions, dest), find_resolution(resolutions, resolvable_link)) + else { + return Ok(()); + }; if dest_res == display_res { + let attr_span = item.attr_span(cx.tcx); let link_span = match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings) { Some((sp, from_expansion)) => { if from_expansion { - return None; + // This `span` comes from macro expansion so skipping it. + return Ok(()); } sp } - None => item.attr_span(cx.tcx), + None => attr_span, }; - let (explicit_span, false) = source_span_for_markdown_range( + let explicit_span = match source_span_for_markdown_range( cx.tcx, doc, &offset_explicit_range(doc, link_range.clone(), b'[', b']'), &item.attrs.doc_strings, - )? - else { + ) { + Some((explicit_span, false)) => explicit_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; - let (display_span, false) = source_span_for_markdown_range( + let display_span = match source_span_for_markdown_range( cx.tcx, doc, resolvable_link_range, &item.attrs.doc_strings, - )? - else { + ) { + Some((display_span, false)) => display_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; - let (def_span, _) = source_span_for_markdown_range( + let def_span = match source_span_for_markdown_range( cx.tcx, doc, &offset_reference_def_range(doc, dest, link_range), &item.attrs.doc_strings, - )?; + ) { + Some((def_span, _)) => def_span, + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } + }; cx.tcx.emit_node_span_lint( crate::lint::REDUNDANT_EXPLICIT_LINKS, @@ -350,7 +434,7 @@ fn check_reference_redundancy( ); } - None + Ok(()) } fn find_resolution(resolutions: &DocLinkResMap, path: &str) -> Option> { diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs index 9de8bbe3b8c98..2975ddfcc68bb 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs @@ -1,43 +1,42 @@ #![deny(rustdoc::redundant_explicit_links)] +use std::clone::Clone; -// Right now, redundant_explicit_links won't produce a warning at all if -// rustdoc isn't able to calculate an accurate span for the link. -// -// If that changes and this test starts to fail, you should add the -// appropriate annotations, and, also, make sure that it doesn't -// suggest a correction that wipes out the `fn` formal signature -// or the `#[inline]` attribute. - -/// [std::clone::Clone]( +/// [Clone]( pub fn split_outer_inner() { //! std::clone::Clone) +//~^^^ ERROR redundant_explicit_links } -/// [std::clone::Clone](std::clone::Clone +/// [Clone](std::clone::Clone pub fn split_outer_inner_b() { //! ) +//~^^^ ERROR redundant_explicit_links } -/// [std::clone::Clone]( +/// [Clone]( #[inline] /// std::clone::Clone) +//~^^^ ERROR redundant_explicit_links pub fn split_attr() { } -/// [std::clone::Clone](std::clone::Clone +/// [Clone](std::clone::Clone #[inline] /// ) +//~^^^ ERROR redundant_explicit_links pub fn split_attr_b() { } -/// [std::clone::Clone]( +/// [Clone]( /// std::clone::Clone) //~^^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] pub fn not_split() { } -/// [std::clone::Clone](std::clone::Clone +/// [Clone](std::clone::Clone /// ) //~^^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] pub fn not_split_b() { } diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr index 3e2e99f10d9f7..ff9bdfb48fc49 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr @@ -1,12 +1,10 @@ error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:33:25 + --> $DIR/redundant_explicit_links_split.rs:4:1 | -LL | /// [std::clone::Clone]( - | ______-----------------__^ - | | | - | | because label contains path that resolves to same destination -LL | | /// std::clone::Clone) - | |_____________________^ explicit target is redundant +LL | / /// [Clone]( +LL | | pub fn split_outer_inner() { +LL | | //! std::clone::Clone) + | |__________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination | = note: when a link's destination is not specified, the label is used to resolve intra-doc links @@ -15,18 +13,64 @@ note: the lint level is defined here | LL | #![deny(rustdoc::redundant_explicit_links)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:10:1 + | +LL | / /// [Clone](std::clone::Clone +LL | | pub fn split_outer_inner_b() { +LL | | //! ) + | |_________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:16:1 + | +LL | / /// [Clone]( +LL | | #[inline] +LL | | /// std::clone::Clone) + | |______________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:23:1 + | +LL | / /// [Clone](std::clone::Clone +LL | | #[inline] +LL | | /// ) + | |_____^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:30:13 + | +LL | /// [Clone]( + | ______-----__^ + | | | + | | because label contains path that resolves to same destination +LL | | /// std::clone::Clone) + | |_____________________^ explicit target is redundant + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links help: remove explicit link target | -LL - /// [std::clone::Clone]( +LL - /// [Clone]( LL - /// std::clone::Clone) -LL + /// [std::clone::Clone] +LL + /// [Clone] | error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:39:25 + --> $DIR/redundant_explicit_links_split.rs:37:13 | -LL | /// [std::clone::Clone](std::clone::Clone - | ______-----------------__^ +LL | /// [Clone](std::clone::Clone + | ______-----__^ | | | | | because label contains path that resolves to same destination LL | | /// ) @@ -36,10 +80,10 @@ LL | | /// ) the label is used to resolve intra-doc links help: remove explicit link target | -LL - /// [std::clone::Clone](std::clone::Clone +LL - /// [Clone](std::clone::Clone LL - /// ) -LL + /// [std::clone::Clone] +LL + /// [Clone] | -error: aborting due to 2 previous errors +error: aborting due to 6 previous errors From 74c203dfbdccbc169eea104bf09778c2b8912bc7 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 20:35:58 -0700 Subject: [PATCH 14/78] Add clarifying comment for shrink_to_hi --- compiler/rustc_resolve/src/rustdoc.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 42a5005552825..7ee5f04ef7243 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -652,6 +652,8 @@ pub fn source_span_for_markdown_range_inner( span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0; let has_fragment = fragments .iter() + // `source_line_span` might contain indentation that `fragment.span` doesn't contain, + // so `shrink_to_hi()` removes it .any(|fragment| fragment.span.contains(source_line_span.shrink_to_hi())); // Since we're counting bytes, `prev_line_bytes` includes the "\n". prev_lines_bytes += source_line_len + 1; From 162a90c2455223e6fab5d3b540c07851e8818a92 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 18 Jun 2026 12:12:03 -0700 Subject: [PATCH 15/78] Handle block doc comments better --- compiler/rustc_resolve/src/rustdoc.rs | 46 ++++++- .../lints/redundant_explicit_links_split.rs | 58 +++++++++ .../redundant_explicit_links_split.stderr | 118 +++++++++++++++++- 3 files changed, 214 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 7ee5f04ef7243..f1e94b415787b 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -650,14 +650,50 @@ pub fn source_span_for_markdown_range_inner( let source_line_len = u32::try_from(source_line.len()).unwrap(); let source_line_span = span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0; - let has_fragment = fragments + let fragment = fragments .iter() - // `source_line_span` might contain indentation that `fragment.span` doesn't contain, - // so `shrink_to_hi()` removes it - .any(|fragment| fragment.span.contains(source_line_span.shrink_to_hi())); + // `source_line_span` might contain indentation that `fragment.span` doesn't contain + .find(|fragment| fragment.span.overlaps(source_line_span)); // Since we're counting bytes, `prev_line_bytes` includes the "\n". prev_lines_bytes += source_line_len + 1; - if has_fragment && let Some(offset) = source_line.find(md_line) { + if let Some(fragment) = fragment + && let Some(offset) = source_line.find(md_line) + { + if fragment.span.lo() > source_line_span.lo() + && source_line + [..usize::try_from(fragment.span.lo().0 - source_line_span.lo().0).unwrap()] + .chars() + .any(|c| !c.is_whitespace()) + { + // Make sure anything between the start of this line and the fragment itself is just indentation. + // Because source_line is built by splitting the span that covers all fragments, this only finds + // characters *between* doc comments, not characters before or after doc comments. + // + // 1| /** doc */ + // 2| #[inline] /** doc2 */ + // ^^^^^^^^^ + // | this + // + // 3| fn foo() {} + return None; + } + if fragment.span.hi() < source_line_span.hi() + && source_line + [usize::try_from(fragment.span.hi().0 - source_line_span.lo().0).unwrap()..] + .chars() + .any(|c| !c.is_whitespace()) + { + // Make sure anything between the start of this line and the fragment itself is just indentation. + // Because source_line is built by splitting the span that covers all fragments, this only finds + // characters *between* doc comments, not characters before or after doc comments. + // 1| /** doc */ #[inline] + // ^^^^^^^^^ + // | this + // + // 2| /** doc2 */ fn foo() {} + // 3| fn foo() {} + return None; + } if line_no == starting_line { start_bytes += offset; diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs index 2975ddfcc68bb..eebc6a0aa023c 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs @@ -27,6 +27,64 @@ pub fn split_attr() { pub fn split_attr_b() { } +/** [Clone]( */ #[inline] /** std::clone::Clone) */ +//~^ ERROR redundant_explicit_links +pub fn split_blockcomment_attr() { +} + +/** [Clone](std::clone::Clone */ #[inline] /** ) */ +//~^ ERROR redundant_explicit_links +pub fn split_blockcomment_attr_b() { +} + +/** [Clone]( */ +#[inline] +/** std::clone::Clone) */ +//~^^^ ERROR redundant_explicit_links +pub fn split_blockcomment_multiline_attr() { +} + +/** [Clone](std::clone::Clone */ +#[inline] +/** ) */ +//~^^^ ERROR redundant_explicit_links +pub fn split_blockcomment_multiline_attr_b() { +} + +/** [Clone]( */ #[inline] +/** std::clone::Clone) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_twoline_attr() { +} + +/** [Clone](std::clone::Clone */ #[inline] +/** ) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_twoline_attr_b() { +} + +/** [Clone]( */ +#[inline] /** std::clone::Clone) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_secondline_attr() { +} + +/** [Clone](std::clone::Clone */ +#[inline] /** ) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_secondline_attr_b() { +} + +/** [Clone](std::clone::Clone) */ #[inline] +//~^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] +pub fn split_blockcomment_singleline_attr() { +} + +/** [Clone](std::clone::Clone) */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } +//~^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] + /// [Clone]( /// std::clone::Clone) //~^^ ERROR redundant_explicit_links diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr index ff9bdfb48fc49..bfab5b751eedf 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr @@ -48,7 +48,119 @@ LL | | /// ) the label is used to resolve intra-doc links error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:30:13 + --> $DIR/redundant_explicit_links_split.rs:30:1 + | +LL | /** [Clone]( */ #[inline] /** std::clone::Clone) */ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:35:1 + | +LL | /** [Clone](std::clone::Clone */ #[inline] /** ) */ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:40:1 + | +LL | / /** [Clone]( */ +LL | | #[inline] +LL | | /** std::clone::Clone) */ + | |_________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:47:1 + | +LL | / /** [Clone](std::clone::Clone */ +LL | | #[inline] +LL | | /** ) */ + | |________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:54:1 + | +LL | / /** [Clone]( */ #[inline] +LL | | /** std::clone::Clone) */ + | |_________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:60:1 + | +LL | / /** [Clone](std::clone::Clone */ #[inline] +LL | | /** ) */ + | |________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:66:1 + | +LL | / /** [Clone]( */ +LL | | #[inline] /** std::clone::Clone) */ + | |___________________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:72:1 + | +LL | / /** [Clone](std::clone::Clone */ +LL | | #[inline] /** ) */ + | |__________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:78:13 + | +LL | /** [Clone](std::clone::Clone) */ #[inline] + | ----- ^^^^^^^^^^^^^^^^^ explicit target is redundant + | | + | because label contains path that resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +help: remove explicit link target + | +LL - /** [Clone](std::clone::Clone) */ #[inline] +LL + /** [Clone] */ #[inline] + | + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:84:13 + | +LL | /** [Clone](std::clone::Clone) */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } + | ----- ^^^^^^^^^^^^^^^^^ explicit target is redundant + | | + | because label contains path that resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +help: remove explicit link target + | +LL - /** [Clone](std::clone::Clone) */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } +LL + /** [Clone] */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } + | + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:88:13 | LL | /// [Clone]( | ______-----__^ @@ -67,7 +179,7 @@ LL + /// [Clone] | error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:37:13 + --> $DIR/redundant_explicit_links_split.rs:95:13 | LL | /// [Clone](std::clone::Clone | ______-----__^ @@ -85,5 +197,5 @@ LL - /// ) LL + /// [Clone] | -error: aborting due to 6 previous errors +error: aborting due to 16 previous errors From b8c8868d0255aff1c55d64d950e28000a70691a6 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Fri, 26 Jun 2026 20:17:36 +0000 Subject: [PATCH 16/78] Address review --- .../src/traits/outlives_for_liveness.rs | 204 +++++++++++------- .../gat-outlives-env.edition2015.stderr | 2 +- .../gat-outlives-env.edition2024.stderr | 2 +- .../gat-outlives-env.polonius_alpha.stderr | 2 +- .../alias-liveness/gat-outlives-env.rs | 9 +- .../gat-outlives-implied.edition2024.stderr | 2 +- ...gat-outlives-implied.polonius_alpha.stderr | 2 +- .../alias-liveness/gat-outlives-implied.rs | 1 - 8 files changed, 138 insertions(+), 86 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs index acfcb4d5d2611..69df20499f80c 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -77,10 +77,16 @@ pub(crate) fn live_args_for_alias_from_outlives_bounds<'tcx>( // doesn't capture *any* regions, so we can skip visiting any regions at all. // // I was originally a bit concerned about something like `'a: 'static`, and - // whether or not we need to mark `'a` as live. I don't think *today* we do, - // since I think regions that outlive `'static` are special enough, but I - // *could* imagine some world where we need to be more careful about this. - // Given I can't find a test that goes wrong, I'm going to leave in this optimization. + // whether or not we need to mark `'a` as live. I don't think that we do. + // + // To dig in a bit: Think about the function using this alias. For the alias + // to be well-formed, then it must be proven that the arg (`'a` in this case) + // outlives `'static`. Well, if that is proven *once*, then it must be true + // across the entire function (because `'static` is free). + // + // I think this similarly applies to any other free region, like `'a: 'b` + // where `'b` is *also* free. Though, we of course can't know *here* which + // regions are going to be instantiated with free regions. if outlives_regions.contains(&tcx.lifetimes.re_static) { tracing::debug!("alias has a 'static outlives bound, so skipping visiting any regions"); return Some(ty::EarlyBinder::bind(tcx, vec![])); @@ -96,16 +102,16 @@ pub(crate) fn live_args_for_alias_from_outlives_bounds<'tcx>( tracing::debug!(?args_known_to_outlive); let mut live_args: Option>> = None; for outlives_region in outlives_regions { - let Some(outlives_regions) = + let Some(outlives_params) = args_known_to_outlive.iter().find(|(r, _)| *r == outlives_region) else { continue; }; - let new_live_args = outlives_regions.1.iter().copied().collect(); - live_args = Some(match live_args.take() { - None => new_live_args, - Some(prev) => prev.intersection(&new_live_args).copied().collect(), - }); + let new_live_args = outlives_params.1.iter().copied().collect(); + match &mut live_args { + None => live_args = Some(new_live_args), + Some(prev) => *prev = prev.intersection(&new_live_args).copied().collect(), + }; } live_args.map(|c| ty::EarlyBinder::bind(tcx, c.into_iter().collect())) } @@ -139,6 +145,31 @@ pub(crate) fn args_known_to_outlive_alias_params<'tcx>( } } +/// For each *captured* region of this alias, compute the *captured* identity +/// args that are known to outlive it, given the definition of the opaque type +/// in the the *parent* context. +/// +/// Some examples: +/// ```ignore (illustrative) +/// // Returns `[('a, ['a]), ('b, ['b])]` +/// fn foo<'a, 'b>() -> impl Sized + use<'a, 'b> {} +/// +/// // Returns `[('a, ['a]), ('b, ['b])]` +/// fn foo<'a: 'a, 'b>() -> impl Sized + use<'a, 'b> {} +/// +/// // Returns `[('a, ['a])]` +/// fn foo<'a, 'b>() -> impl Sized + use<'a> {} +/// +/// // Returns `[('a, ['a, 'b]), ('b, ['b])]` +/// fn foo<'a, 'b: 'a>() -> impl Sized + use<'a, 'b> {} +/// +/// // Returns `[('a, ['a, 'b]), ('b, ['b])]` +/// fn foo<'a, 'b>(_: &'a &'b ()) -> impl Sized + use<'a, 'b> {} +/// ``` +/// +/// Importantly: +/// - *All* captured regions are considered (not just those in outlives bounds) +/// - It doesn't matter if the captured region is early-bound or late-bound #[tracing::instrument(level = "debug", skip(tcx), ret)] pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( tcx: TyCtxt<'tcx>, @@ -150,26 +181,22 @@ pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( // For implied bounds, we need the set of WF types from the parents. // - For functions, this is all the input and output types. - // - For type alias, there are no implied bounds, so this is empty. (FIXME: the alias type itself probably should be here?) + // - For type alias, there are no implied bounds, so this is empty. let (parent_def_id, wf_tys) = match tcx.opaque_ty_origin(def_id) { rustc_hir::OpaqueTyOrigin::FnReturn { parent, .. } - | rustc_hir::OpaqueTyOrigin::AsyncFn { parent, .. } => { - let fn_sig = tcx.fn_sig(parent).instantiate_identity().skip_norm_wip(); - let (liberated_fn_sig, _) = tcx.instantiate_bound_regions(fn_sig, |br| { - let kind = ty::LateParamRegionKind::from_bound(br.var, br.kind); - ty::Region::new_late_param(tcx, parent, kind) - }); - let wf_tys = liberated_fn_sig.inputs_and_output.iter().collect(); + | rustc_hir::OpaqueTyOrigin::AsyncFn { parent, .. } + | rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => { + let wf_tys = FxIndexSet::from_iter( + tcx.assumed_wf_types(parent.expect_local()).iter().map(|(ty, _)| *ty), + ); (parent, wf_tys) } - rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => (parent, FxIndexSet::default()), }; let parent_param_env = tcx.param_env(parent_def_id); tracing::debug!(?parent_param_env); // Map the outlives regions to the parent regions let generics = tcx.generics_of(def_id); - let opaque_captured_lifetimes = tcx.opaque_captured_lifetimes(def_id); let mut parent_outlives_regions = Vec::with_capacity(generics.own_params.len()); for opaque_arg in self_identity_args[generics.parent_count..].iter() { let Some(opaque_region) = opaque_arg.as_region() else { @@ -179,18 +206,18 @@ pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( ty::ReEarlyParam(ebr) => generics.param_at(ebr.index as usize, tcx).def_id, _ => panic!("unexpected region `{opaque_region}` in opaque bounds"), }; - let region_param = - generics.own_params.iter().find(|param| param.def_id == region_def_id).unwrap(); - let (_, opaque_region_def_id) = opaque_captured_lifetimes - .iter() - .find(|(_, opaque_r)| opaque_r.to_def_id() == region_def_id) - .unwrap(); - let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_region_def_id); - tracing::debug!(?region_def_id, ?region_param, ?parent_region); + let parent_region = + tcx.map_opaque_lifetime_to_parent_lifetime(region_def_id.expect_local()); + tracing::debug!(?region_def_id, ?parent_region); parent_outlives_regions.push((parent_region, opaque_region)); } tracing::debug!(?parent_outlives_regions); + // For every captured region, we want to consider outlived args from two sources: + // 1) *Types*: These come from *parent* generics (and are not duplicated to the opaque) + // 2) *Captured Regions* + // + // In both cases, we need to check known outlives for the *parent* region, because that's where the param_env and wf_tys are. for (parent_outlived_region, opaque_outlived_region) in parent_outlives_regions.iter() { let mut opaque_outlives_args = Vec::with_capacity(self_identity_args.len()); for parent_outlives_arg in self_identity_args[..generics.parent_count].iter() { @@ -216,9 +243,7 @@ pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( opaque_outlives_args.push(*parent_outlives_arg); } - for (_, opaque_lt) in opaque_captured_lifetimes.iter() { - let parent_outlives_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_lt); - + for &(parent_outlives_region, opaque_region) in parent_outlives_regions.iter() { let region_outlives = parent_outlives_region == *parent_outlived_region || region_known_to_outlive( tcx, @@ -232,14 +257,6 @@ pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( continue; } - let param = generics - .own_params - .iter() - .find(|param| param.def_id == opaque_lt.to_def_id()) - .unwrap(); - let opaque_region = - ty::Region::new_early_param(tcx, param.to_early_bound_region_data()); - opaque_outlives_args.push(opaque_region.into()); } @@ -314,13 +331,14 @@ pub(crate) fn args_known_to_outlive_associated_type_params<'tcx>( /// /// Returns `None` if the clause doesn't apply to `ty` or gives us no information. #[tracing::instrument(level = "debug", skip(tcx), ret)] -fn live_args_for_clause<'tcx>( +fn live_args_for_outlives_clause<'tcx>( tcx: TyCtxt<'tcx>, alias_def_id: DefId, ty: Ty<'tcx>, - clause: ty::Clause<'tcx>, -) -> Option>> { - let outlives = clause.as_type_outlives_clause()?; + outlives: ty::Binder<'tcx, ty::TypeOutlivesPredicate<'tcx>>, +) -> Option>>> { + // N.B. it's okay to skip the binder here (and in the rest of the function), + // because all variables under binders do not escape let ty::Alias(_, ty::AliasTy { kind: clause_alias_kind, args: clause_args, .. }) = *outlives.skip_binder().0.kind() else { @@ -350,6 +368,9 @@ fn live_args_for_clause<'tcx>( let clause_identity_args = ty::GenericArgs::identity_for_item(tcx, alias_def_id); match bound_region.kind() { // The underlying type must outlive `'static`, so it can't capture any of the args at all. + // + // Of course, you may ask: "what if the function has a `'a: 'static` bound?" See the corresponding + // comment in `live_args_for_alias_from_outlives_bounds` for why we don't need to worry about that. ty::ReStatic => Some(FxIndexSet::default()), ty::ReBound(_, br) => { // The bound is one of the clause's higher-ranked vars. Find the arg @@ -357,14 +378,14 @@ fn live_args_for_clause<'tcx>( // all args that are known to outlive one of those positions given // the alias's declared bounds -- only those can be captured by the // underlying type. - let mut bound_regions = Vec::new(); + let mut outlived_regions = Vec::new(); for (clause_arg, identity_arg) in clause_args.iter().zip(clause_identity_args.iter()) { match clause_arg.kind() { ty::GenericArgKind::Lifetime(r) => { if let ty::ReBound(_, arg_br) = r.kind() && arg_br.var == br.var { - bound_regions.push(identity_arg.expect_region()); + outlived_regions.push(identity_arg.expect_region()); } } ty::GenericArgKind::Type(_) | ty::GenericArgKind::Const(_) => { @@ -379,7 +400,7 @@ fn live_args_for_clause<'tcx>( } } } - if bound_regions.is_empty() { + if outlived_regions.is_empty() { // The bound var doesn't appear in the args at all, so the clause // requires the underlying type to outlive *every* region, which // is equivalent to a `'static` bound. @@ -390,30 +411,47 @@ fn live_args_for_clause<'tcx>( // of the bound var's positions (they're all instantiated to the same // region at any use site this clause applies to). let args_known_to_outlive = tcx.args_known_to_outlive_alias_params(alias_def_id); - tracing::debug!(?bound_regions, ?args_known_to_outlive); + tracing::debug!(?outlived_regions, ?args_known_to_outlive); let mut capturable_args = FxIndexSet::default(); - for &bound_region in &bound_regions { + for &outlived_region in &outlived_regions { + // There's a bit of a dance here around `Earlybinder::skip_binder` + // and then later a `Earlybinder::bind`. This is because there's + // no real good way today to move the `EarlyBinder` inward + // declaratively without cloning the entire thing. let (_, outliving_args) = args_known_to_outlive .as_ref() .skip_binder() .iter() - .find(|(region, _)| *region == bound_region) + .find(|(region, _)| *region == outlived_region) .unwrap(); - capturable_args.extend(outliving_args.iter().copied()); + capturable_args + .extend(outliving_args.iter().copied().map(|a| ty::EarlyBinder::bind(tcx, a))); } Some(capturable_args) } - // A free region (e.g. `for<'a> T::Assoc<'a, 'b>: 'b`): no higher-ranked - // arg of the alias can be proven (by a caller) to outlive a free region - // today, and pinned args only ever match universal regions (which are - // always live), so we conservatively treat this as giving no - // restriction. + // A free region (e.g. `for
T::Assoc<'a, 'x>: 'x`, where `'x` is free). + // This is effectively the same as `for<'a, 'b> T::Assoc<'a, 'b>: 'b`, + // but that only is sound if we either know that the second substituted + // lifetime equals `'x` or if we *constrain* that lifetime to be `'x`. // - // NB: if we ever get implied bounds inside binders, a bound var *could* - // be assumed to outlive a free region (e.g. `for<'a> T::Assoc<'x, 'a>: 'x` - // with a declared `'b: 'a` on the assoc type implies `'a: 'x` under the - // binder), so this would need revisiting -- though the result would - // still only need to include bound-var positions. + // In either case, something like this doesn't work today: + // ```ignore (illustrative) + // fn bar<'a, 'b>(a: &'a mut (), b: &'b ()) -> ::Assoc<'a, 'b> { b } + // fn foo<'x>() + // where + // for<'h> ::Assoc<'h, 'x>: 'x, + // { + // let a = &mut (); + // let b: &'x () = &(); + // let val1 = rpit(a, b); + // let val2 = rpit(a, b); + // drop(val1); + // drop(val32); + // } + // ``` + // So, we conservatively treat this as giving no restriction on which args can be captured. + ty::ReEarlyParam(..) => None, + // Don't know that we actually hit this (maybe `ReError`), go ahead and be conservative. _ => None, } } @@ -452,12 +490,6 @@ where return; } - // FIXME: Don't consider alias bounds on types that have escaping bound - // vars. See #117455. - if ty.has_escaping_bound_vars() { - return ty.super_visit_with(self); - } - match *ty.kind() { // We can prove that an alias is live two ways: // 1. All the components are live. @@ -486,20 +518,38 @@ where | ty::AliasTyKind::Opaque { def_id } | ty::AliasTyKind::Free { def_id } => def_id, }; - let mut capturable: Option>> = None; - let mut restrict = |capturable_args: FxIndexSet>| { - capturable = Some(match capturable.take() { - None => capturable_args, - Some(prev) => prev.intersection(&capturable_args).copied().collect(), - }); - }; + let mut capturable: Option< + FxIndexSet>>, + > = None; + let mut restrict = + |capturable_args: FxIndexSet>>| { + match &mut capturable { + None => capturable = Some(capturable_args), + Some(prev) => { + *prev = prev.intersection(&capturable_args).copied().collect() + } + }; + }; if let Some(live_args) = tcx.live_args_for_alias_from_outlives_bounds(kind) { - restrict(live_args.as_ref().skip_binder().iter().copied().collect()); + restrict( + live_args + .as_ref() + .skip_binder() + .iter() + .copied() + .map(|a| ty::EarlyBinder::bind(tcx, a)) + .collect(), + ); } for clause in param_env.caller_bounds() { - if let Some(capturable_args) = live_args_for_clause(tcx, def_id, ty, clause) { + let Some(outlives) = clause.as_type_outlives_clause() else { + continue; + }; + if let Some(capturable_args) = + live_args_for_outlives_clause(tcx, def_id, ty, outlives) + { restrict(capturable_args); } } @@ -508,9 +558,7 @@ where match capturable { Some(capturable_args) => { for arg in capturable_args { - let arg = ty::EarlyBinder::bind(tcx, arg) - .instantiate(tcx, args) - .skip_norm_wip(); + let arg = arg.instantiate(tcx, args).skip_norm_wip(); arg.visit_with(self); } } diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr index 66d530bc4ecad..9b71acde986f7 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr @@ -1,5 +1,5 @@ error[E0499]: cannot borrow `t` as mutable more than once at a time - --> $DIR/gat-outlives-env.rs:44:13 + --> $DIR/gat-outlives-env.rs:49:13 | LL | let a = t.changes(static_unit); | - first mutable borrow occurs here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr index 66d530bc4ecad..9b71acde986f7 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr @@ -1,5 +1,5 @@ error[E0499]: cannot borrow `t` as mutable more than once at a time - --> $DIR/gat-outlives-env.rs:44:13 + --> $DIR/gat-outlives-env.rs:49:13 | LL | let a = t.changes(static_unit); | - first mutable borrow occurs here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr index 66d530bc4ecad..9b71acde986f7 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr @@ -1,5 +1,5 @@ error[E0499]: cannot borrow `t` as mutable more than once at a time - --> $DIR/gat-outlives-env.rs:44:13 + --> $DIR/gat-outlives-env.rs:49:13 | LL | let a = t.changes(static_unit); | - first mutable borrow occurs here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs index eaf79b06e8d1c..1c1b728e486b1 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs @@ -5,6 +5,9 @@ //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next +// Demonstrates that you can use a outlives bound on an associated type in a +// where clause to restrict the lifetimes that may be contained within. + trait Updater { type Changes<'a, 'b: 'a> where @@ -12,6 +15,7 @@ trait Updater { fn changes<'a, 'b>(&'a mut self, _value: &'b u8) -> Self::Changes<'a, 'b>; } +// Nothing can be captured. fn overlapping_mut(mut t: T) where T: Updater, @@ -22,6 +26,7 @@ where let b = t.changes(static_unit); } +// Only `&mut self` is captured not `&'static u8`, and that borrow is killed on the next call. fn overlapping_mut2(mut t: T) where T: Updater, @@ -30,10 +35,10 @@ where let static_unit: &'static u8 = &0; let a = t.changes(static_unit); let b = t.changes(static_unit); - // ^ These don't error, because the underlying type can't capture `&self`: - // it would have to outlive `'b` for arbitrary `'a` and `'b`. } +// Both `&mut self` and `&'static u8` are captured, because the `'static` lifetime +// makes the associated type live for the entire function body. fn overlapping_mut3<'b, T>(mut t: T) where T: Updater, diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr index 2d733d142f0f6..9317846041925 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr @@ -1,5 +1,5 @@ error[E0506]: cannot assign to `cluster` because it is borrowed - --> $DIR/gat-outlives-implied.rs:20:9 + --> $DIR/gat-outlives-implied.rs:19:9 | LL | let changes = self.changes(&cluster); | -------- `cluster` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr index 2d733d142f0f6..9317846041925 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr @@ -1,5 +1,5 @@ error[E0506]: cannot assign to `cluster` because it is borrowed - --> $DIR/gat-outlives-implied.rs:20:9 + --> $DIR/gat-outlives-implied.rs:19:9 | LL | let changes = self.changes(&cluster); | -------- `cluster` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs index 4248b0063179d..d5afb11742c35 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs @@ -6,7 +6,6 @@ // Tests that liveness for regions in associated types considers outlives // bounds, and the transitive implied outlives bounds from those. - trait Updater { // Because `'b` is known to outlive `'a`, then we must consider that `'b` // may be live in `Self::Changes<'a, 'b>`. From 4d1093d2d22cf5642a2a8abb84a7e5285327aaaf Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Fri, 3 Jul 2026 12:10:05 +0300 Subject: [PATCH 17/78] Do not inherit `ConstArgHasType` predicates when it is applied to const from delegation parent --- compiler/rustc_hir_analysis/src/delegation.rs | 64 +++++++++++++++++-- .../generics/const-predicates-ice-158675.rs | 30 +++++++++ .../const-predicates-ice-158675.stderr | 42 ++++++++++++ 3 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 tests/ui/delegation/generics/const-predicates-ice-158675.rs create mode 100644 tests/ui/delegation/generics/const-predicates-ice-158675.stderr diff --git a/compiler/rustc_hir_analysis/src/delegation.rs b/compiler/rustc_hir_analysis/src/delegation.rs index 58474f0ee2e38..40adfeadc4411 100644 --- a/compiler/rustc_hir_analysis/src/delegation.rs +++ b/compiler/rustc_hir_analysis/src/delegation.rs @@ -4,7 +4,7 @@ use std::debug_assert_matches; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{DelegationSelfTyPropagationKind, PathSegment}; @@ -21,6 +21,7 @@ type RemapTable = FxHashMap; struct ParamIndexRemapper<'tcx> { tcx: TyCtxt<'tcx>, remap_table: RemapTable, + delegation_parent_consts: FxHashSet, } impl<'tcx> TypeFolder> for ParamIndexRemapper<'tcx> { @@ -337,7 +338,7 @@ fn create_generic_args<'tcx>( delegation_id: LocalDefId, mut parent_args: &[ty::GenericArg<'tcx>], mut child_args: &[ty::GenericArg<'tcx>], -) -> Vec> { +) -> (Vec>, &'tcx [ty::GenericArg<'tcx>]) { let delegation_generics = tcx.generics_of(delegation_id); let delegation_args = ty::GenericArgs::identity_for_item(tcx, delegation_id); @@ -389,7 +390,7 @@ fn create_generic_args<'tcx>( let zero_self = zero_self.as_ref().into_iter(); let after_lifetimes_self = after_lifetimes_self.as_ref().into_iter(); - zero_self + let args = zero_self .chain(delegation_parent_args) .chain(parent_args.iter().filter(|a| a.as_region().is_some())) .chain(child_args.iter().filter(|a| a.as_region().is_some())) @@ -398,7 +399,9 @@ fn create_generic_args<'tcx>( .chain(child_args.iter().filter(|a| a.as_region().is_none())) .chain(synth_args) .copied() - .collect::>() + .collect::>(); + + (args, delegation_parent_args) } pub(crate) fn inherit_predicates_for_delegation_item<'tcx>( @@ -434,6 +437,44 @@ pub(crate) fn inherit_predicates_for_delegation_item<'tcx>( continue; } + // If we have a constant in parent or child args that came from delegation + // parent: + // ```rust + // trait Trait { /* .. */} + // impl S { + // reuse Trait::, N>::foo; + // } + // ``` + // Then if we inherit const predicate from `Trait` then we end up with + // two `ConstArgHasType` for `N` constant: + // 1) ConstArgHasType(N/#0, bool) from `Trait` + // 2) ConstArgHasType(N/#0, usize) from delegation parent + // So in case the constant came from delegation parent we will not inherit + // ConstArgHasType from signature. + // The check is so complicated because we build generic args for signature + // and predicates inheritance, for the example above it will be + // `args = [S, N/#0, S, N/#0]`, where + // args[0] - Self type, args[1] - delegation parent const, args[2] - first + // arg of callee path, args[3] - second arg of callee path. + // When processing predicate ConstArgHasType(B/#2, bool) + // from delegation signature (`Trait::foo`), we need to map `B/#2` into some + // arg from `args`. The mapping which is built by `create_mapping` function is: + // `{0: 0, 2: 3, 1: 2}`, so as `B/#2` has index `2` it is mapped into third + // arg from `args` - `N/#0`. After we obtained mapped const param, we check if + // it came from delegation parent, and if so we do not process its `ConstArgHasType` + // predicate. + // (Issue #158675). + if let ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, _)) = + pred.0.as_predicate().fold_with(&mut self.folder).kind().skip_binder() + { + let unnorm_const = EarlyBinder::bind(self.tcx, ct).instantiate(self.tcx, args); + if let ty::ConstKind::Param(param) = unnorm_const.skip_norm_wip().kind() + && self.folder.delegation_parent_consts.contains(¶m) + { + continue; + } + } + let new_pred = pred.0.fold_with(&mut self.folder); self.preds.push(( EarlyBinder::bind(self.tcx, new_pred) @@ -497,10 +538,21 @@ fn create_folder_and_args<'tcx>( parent_args: &'tcx [ty::GenericArg<'tcx>], child_args: &'tcx [ty::GenericArg<'tcx>], ) -> (ParamIndexRemapper<'tcx>, Vec>) { - let args = create_generic_args(tcx, sig_id, def_id, parent_args, child_args); + let (args, delegation_parent_args) = + create_generic_args(tcx, sig_id, def_id, parent_args, child_args); + let remap_table = create_mapping(tcx, sig_id, def_id); - (ParamIndexRemapper { tcx, remap_table }, args) + let delegation_parent_consts = delegation_parent_args + .iter() + .filter_map(|a| { + a.as_const().and_then(|c| { + if let ty::ConstKind::Param(param) = c.kind() { Some(param) } else { None } + }) + }) + .collect(); + + (ParamIndexRemapper { tcx, remap_table, delegation_parent_consts }, args) } fn check_constraints<'tcx>( diff --git a/tests/ui/delegation/generics/const-predicates-ice-158675.rs b/tests/ui/delegation/generics/const-predicates-ice-158675.rs new file mode 100644 index 0000000000000..2e88ca8135534 --- /dev/null +++ b/tests/ui/delegation/generics/const-predicates-ice-158675.rs @@ -0,0 +1,30 @@ +#![feature(fn_delegation)] + +mod original_ice { + struct S; + + trait Trait { + fn fun(); + } + + impl S { + reuse Trait::, N>::fun; + //~^ ERROR: the constant `N` is not of type `bool` + } +} + +mod with_child_constants { + struct S; + + trait Trait { + fn fun(); + } + + impl S { + reuse Trait::, N>::fun::; + //~^ ERROR: the constant `N` is not of type `bool` + //~| ERROR: the constant `N` is not of type `char` + } +} + +fn main() {} diff --git a/tests/ui/delegation/generics/const-predicates-ice-158675.stderr b/tests/ui/delegation/generics/const-predicates-ice-158675.stderr new file mode 100644 index 0000000000000..6dbf017294822 --- /dev/null +++ b/tests/ui/delegation/generics/const-predicates-ice-158675.stderr @@ -0,0 +1,42 @@ +error: the constant `N` is not of type `bool` + --> $DIR/const-predicates-ice-158675.rs:11:29 + | +LL | reuse Trait::, N>::fun; + | ^ expected `bool`, found `usize` + | +note: required by a const generic parameter in `original_ice::Trait::fun` + --> $DIR/const-predicates-ice-158675.rs:6:20 + | +LL | trait Trait { + | ^^^^^^^^^^^^^ required by this const generic parameter in `Trait::fun` +LL | fn fun(); + | --- required by a bound in this associated function + +error: the constant `N` is not of type `bool` + --> $DIR/const-predicates-ice-158675.rs:24:29 + | +LL | reuse Trait::, N>::fun::; + | ^ expected `bool`, found `usize` + | +note: required by a const generic parameter in `with_child_constants::Trait::fun` + --> $DIR/const-predicates-ice-158675.rs:19:20 + | +LL | trait Trait { + | ^^^^^^^^^^^^^ required by this const generic parameter in `Trait::fun` +LL | fn fun(); + | --- required by a bound in this associated function + +error: the constant `N` is not of type `char` + --> $DIR/const-predicates-ice-158675.rs:24:39 + | +LL | reuse Trait::, N>::fun::; + | ^ expected `char`, found `usize` + | +note: required by a const generic parameter in `with_child_constants::Trait::fun` + --> $DIR/const-predicates-ice-158675.rs:20:16 + | +LL | fn fun(); + | ^^^^^^^^^^^^^ required by this const generic parameter in `Trait::fun` + +error: aborting due to 3 previous errors + From 1912e4fcc1026f93260522a892473003445f3500 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Fri, 3 Jul 2026 17:04:09 +0000 Subject: [PATCH 18/78] Rename variable and add comment --- .../src/traits/outlives_for_liveness.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs index 69df20499f80c..191f75628a69e 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -195,7 +195,19 @@ pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( let parent_param_env = tcx.param_env(parent_def_id); tracing::debug!(?parent_param_env); - // Map the outlives regions to the parent regions + // Map the outlives regions to the parent regions. + // If we have `fn foo<'a>() -> impl Sized + 'a`, then this gets lowered as + // ```ignore (illustrative) + // opaque foo_opaque<'a0>: Sized + 'a0; + // fn foo<'a>() -> foo::<'a>::foo_opaque<'a> { ... } + // ``` + // This maps `'a0` to `'a`, because that is what will be used to get the + // explicit and implied outlives relations. + // + // I suppose, an alternative way to do this would be iterate through all the + // *parent* regions and then find those that are captured. This should be + // basically equivalent (except with the added frustration of needing to + // build a `Region` from the opaque region's `LocalDefId`). let generics = tcx.generics_of(def_id); let mut parent_outlives_regions = Vec::with_capacity(generics.own_params.len()); for opaque_arg in self_identity_args[generics.parent_count..].iter() { @@ -364,9 +376,9 @@ fn live_args_for_outlives_clause<'tcx>( ty, )?; - let bound_region = outlives.skip_binder().1; + let outlived_region = outlives.skip_binder().1; let clause_identity_args = ty::GenericArgs::identity_for_item(tcx, alias_def_id); - match bound_region.kind() { + match outlived_region.kind() { // The underlying type must outlive `'static`, so it can't capture any of the args at all. // // Of course, you may ask: "what if the function has a `'a: 'static` bound?" See the corresponding From b66097bc94c055e77bcc84ae05da573ca3d3f7af Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Mon, 11 May 2026 11:10:00 -0400 Subject: [PATCH 19/78] Move DuplicateEiiImpls to rustc_middle --- compiler/rustc_middle/src/error.rs | 29 ++++++++++++++++++++++++ compiler/rustc_passes/src/diagnostics.rs | 29 ------------------------ compiler/rustc_passes/src/eii.rs | 3 ++- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 2823b7ba4e22e..440634f376301 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -160,3 +160,32 @@ pub(crate) struct IncrementCompilation { pub run_cmd: String, pub dep_node: String, } + +#[derive(Diagnostic)] +#[diag("multiple implementations of `#[{$name}]`")] +pub struct DuplicateEiiImpls { + pub name: Symbol, + + #[primary_span] + #[label("first implemented here in crate `{$first_crate}`")] + pub first_span: Span, + pub first_crate: Symbol, + + #[label("also implemented here in crate `{$second_crate}`")] + pub second_span: Span, + pub second_crate: Symbol, + + #[note("in addition to these two, { $num_additional_crates -> + [one] another implementation was found in crate {$additional_crate_names} + *[other] more implementations were also found in the following crates: {$additional_crate_names} + }")] + pub additional_crates: Option<()>, + + pub num_additional_crates: usize, + pub additional_crate_names: String, + + #[help( + "an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict" + )] + pub help: (), +} diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 92562cc462b87..477f380b1c972 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -1104,35 +1104,6 @@ pub(crate) struct EiiWithoutImpl { pub help: (), } -#[derive(Diagnostic)] -#[diag("multiple implementations of `#[{$name}]`")] -pub(crate) struct DuplicateEiiImpls { - pub name: Symbol, - - #[primary_span] - #[label("first implemented here in crate `{$first_crate}`")] - pub first_span: Span, - pub first_crate: Symbol, - - #[label("also implemented here in crate `{$second_crate}`")] - pub second_span: Span, - pub second_crate: Symbol, - - #[note("in addition to these two, { $num_additional_crates -> - [one] another implementation was found in crate {$additional_crate_names} - *[other] more implementations were also found in the following crates: {$additional_crate_names} - }")] - pub additional_crates: Option<()>, - - pub num_additional_crates: usize, - pub additional_crate_names: String, - - #[help( - "an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict" - )] - pub help: (), -} - #[derive(Diagnostic)] #[diag("function doesn't have a default implementation")] pub(crate) struct FunctionNotHaveDefaultImplementation { diff --git a/compiler/rustc_passes/src/eii.rs b/compiler/rustc_passes/src/eii.rs index c9cfd1e050313..439450fa80bd4 100644 --- a/compiler/rustc_passes/src/eii.rs +++ b/compiler/rustc_passes/src/eii.rs @@ -6,10 +6,11 @@ use std::iter; use rustc_data_structures::fx::FxIndexMap; use rustc_hir::attrs::{EiiDecl, EiiImpl}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::ty::TyCtxt; use rustc_session::config::CrateType; -use crate::diagnostics::{DuplicateEiiImpls, EiiWithoutImpl}; +use crate::diagnostics::EiiWithoutImpl; #[derive(Clone, Copy, Debug)] enum CheckingMode { From 0c822870613e7025f724802f1af6bdbc755a023f Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Mon, 11 May 2026 11:10:36 -0400 Subject: [PATCH 20/78] Reject linked dylib EII default overrides --- compiler/rustc_codegen_ssa/src/back/link.rs | 77 ++++++++++++++++++ compiler/rustc_codegen_ssa/src/base.rs | 80 +++++++++++++++++-- compiler/rustc_codegen_ssa/src/lib.rs | 18 ++++- .../eii/duplicate/auxiliary/dylib_default.rs | 5 ++ .../eii/duplicate/dylib_default_duplicate.rs | 20 +++++ .../duplicate/dylib_default_duplicate.stderr | 15 ++++ 6 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 tests/ui/eii/duplicate/auxiliary/dylib_default.rs create mode 100644 tests/ui/eii/duplicate/dylib_default_duplicate.rs create mode 100644 tests/ui/eii/duplicate/dylib_default_duplicate.stderr diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index c41f6f30a1da3..587515b84a587 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -30,6 +30,7 @@ use rustc_metadata::{ walk_native_lib_search_dirs, }; use rustc_middle::bug; +use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::lint::emit_lint_base; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; @@ -76,6 +77,73 @@ pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) { } } +fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol { + if cnum == LOCAL_CRATE { crate_info.local_crate_name } else { crate_info.crate_name[&cnum] } +} + +fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) { + let Some(eii_linkage) = &crate_info.eii_linkage else { + return; + }; + + // A crate can request multiple linked outputs with overlapping dependency + // formats, so report each underlying conflict once. + let mut emitted = FxHashSet::default(); + + // This needs the dependency formats selected for the final artifact. The + // earlier EII pass still handles missing impls and duplicate explicit impls. + for dependency_formats in crate_info.dependency_formats.values() { + for (eii_index, eii) in eii_linkage.iter().enumerate() { + let mut explicit_impls = + eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); + let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { + continue; + }; + // If the explicit impl is already coming from a dylib, that dylib + // has already resolved the default-vs-explicit choice. + if explicit_impls.next().is_some() + || matches!( + dependency_formats.get(explicit_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) + { + continue; + } + + let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| { + imp.is_default + && matches!( + dependency_formats.get(imp.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) + }); + let Some((default_index, default_impl)) = finalized_default_impls.next() else { + continue; + }; + + if !emitted.insert((eii_index, explicit_index, default_index)) { + continue; + } + + let additional_crate_names = finalized_default_impls + .map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate))) + .collect::>(); + + sess.dcx().emit_err(DuplicateEiiImpls { + name: eii.name, + first_span: explicit_impl.span, + first_crate: eii_impl_crate_name(crate_info, explicit_impl.impl_crate), + second_span: default_impl.span, + second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate), + help: (), + additional_crates: (!additional_crate_names.is_empty()).then_some(()), + num_additional_crates: additional_crate_names.len(), + additional_crate_names: additional_crate_names.join(", "), + }); + } + } +} + /// Performs the linkage portion of the compilation phase. This will generate all /// of the requested outputs for this compilation session. pub fn link_binary( @@ -91,6 +159,7 @@ pub fn link_binary( let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); let mut tempfiles_for_stdout_output: Vec = Vec::new(); let mut rmeta_link_cache = RmetaLinkCache::default(); + let mut checked_eii_linkage = false; for &crate_type in &crate_info.crate_types { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) @@ -116,6 +185,14 @@ pub fn link_binary( }); if outputs.outputs.should_link() { + if !checked_eii_linkage { + sess.time("check_externally_implementable_item_linkage", || { + check_externally_implementable_item_linkage(sess, &crate_info); + }); + sess.dcx().abort_if_errors(); + checked_eii_linkage = true; + } + let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name); let tmpdir = TempDirBuilder::new() .prefix("rustc") diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 4e979df471318..b8b19721ef377 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -1,7 +1,7 @@ -use std::cmp; use std::collections::BTreeSet; use std::sync::Arc; use std::time::{Duration, Instant}; +use std::{cmp, iter}; use itertools::Itertools; use rustc_abi::FIRST_VARIANT; @@ -9,17 +9,17 @@ use rustc_ast::expand::allocator::{ ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, AllocatorKind, AllocatorMethod, AllocatorMethodInput, AllocatorTy, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_data_structures::profiling::{get_resident_set_size, print_time_passes_entry}; use rustc_data_structures::sync::{IntoDynSyncSend, par_map}; use rustc_data_structures::unord::UnordMap; -use rustc_hir::attrs::{DebuggerVisualizerType, OptimizeAttr}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::attrs::{DebuggerVisualizerType, EiiDecl, EiiImpl, OptimizeAttr}; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; use rustc_hir::lang_items::LangItem; use rustc_hir::{ItemId, Target, find_attr}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; -use rustc_middle::middle::dependency_format::Dependencies; +use rustc_middle::middle::dependency_format::{Dependencies, Linkage}; use rustc_middle::middle::exported_symbols::{self, SymbolExportKind}; use rustc_middle::middle::lang_items; use rustc_middle::mir::BinOp; @@ -50,7 +50,8 @@ use crate::mir::operand::OperandValue; use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{ - CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, ModuleCodegen, errors, meth, mir, + CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo, + ModuleCodegen, errors, meth, mir, }; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { @@ -902,6 +903,63 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>( && !tcx.should_codegen_locally(instance) } +fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { + #[derive(Debug)] + struct FoundImpl { + imp: EiiImpl, + impl_crate: CrateNum, + } + + #[derive(Debug)] + struct FoundEii { + decl: EiiDecl, + impls: FxIndexMap, + } + + let mut eiis = FxIndexMap::::default(); + + for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { + for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) { + eiis.entry(*did) + .or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() }) + .impls + .extend( + impls + .into_iter() + .map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })), + ); + } + } + + eiis.into_iter() + .filter_map(|(_, FoundEii { decl, impls })| { + let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count(); + let has_default_impl = impls.values().any(|imp| imp.imp.is_default); + // Only this case needs the link-time check. Missing impls and + // duplicate explicit impls are handled in `rustc_passes`. + (explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo { + name: decl.name.name, + impls: impls + .into_iter() + .map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo { + span: tcx.def_span(impl_did), + impl_crate, + is_default: imp.is_default, + }) + .collect(), + }) + }) + .collect() +} + +fn eii_linkage_needed(dependency_formats: &Dependencies) -> bool { + dependency_formats.values().any(|formats| { + formats + .iter() + .any(|&linkage| matches!(linkage, Linkage::Dynamic | Linkage::IncludedFromDylib)) + }) +} + impl CrateInfo { pub fn new(tcx: TyCtxt<'_>, target_cpu: String) -> CrateInfo { let crate_types = tcx.crate_types().to_vec(); @@ -913,6 +971,13 @@ impl CrateInfo { crate_types.iter().map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))).collect(); let local_crate_name = tcx.crate_name(LOCAL_CRATE); let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind); + let dependency_formats = Arc::clone(tcx.dependency_formats(())); + let eii_linkage = if eii_linkage_needed(&dependency_formats) { + let eii_linkage = collect_eii_linkage(tcx); + (!eii_linkage.is_empty()).then_some(eii_linkage) + } else { + None + }; // This list is used when generating the command line to pass through to // system linker. The linker expects undefined symbols on the left of the @@ -957,7 +1022,8 @@ impl CrateInfo { crate_name: UnordMap::with_capacity(n_crates), used_crates, used_crate_source: UnordMap::with_capacity(n_crates), - dependency_formats: Arc::clone(tcx.dependency_formats(())), + dependency_formats, + eii_linkage, windows_subsystem, natvis_debugger_visualizers: Default::default(), lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx), diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index f3f19f9e90d83..8de2092489e93 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -39,7 +39,7 @@ use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::{self, CrateSource}; use rustc_session::lint::builtin::LINKER_MESSAGES; -use rustc_span::Symbol; +use rustc_span::{Span, Symbol}; pub mod assert_module_sources; pub mod back; @@ -255,6 +255,19 @@ impl SymbolExport { } } +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct EiiLinkageImplInfo { + pub span: Span, + pub impl_crate: CrateNum, + pub is_default: bool, +} + +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct EiiLinkageInfo { + pub name: Symbol, + pub impls: Vec, +} + /// Misc info we load from metadata to persist beyond the tcx. /// /// Note: though `CrateNum` is only meaningful within the same tcx, information within `CrateInfo` @@ -281,6 +294,9 @@ pub struct CrateInfo { pub used_crate_source: UnordMap>, pub used_crates: Vec, pub dependency_formats: Arc, + /// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a + /// later `-Zlink-only` invocation. + pub eii_linkage: Option>, pub windows_subsystem: Option, pub natvis_debugger_visualizers: BTreeSet, pub lint_level_specs: CodegenLintLevelSpecs, diff --git a/tests/ui/eii/duplicate/auxiliary/dylib_default.rs b/tests/ui/eii/duplicate/auxiliary/dylib_default.rs new file mode 100644 index 0000000000000..d1136b0ebd636 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/dylib_default.rs @@ -0,0 +1,5 @@ +#![crate_type = "dylib"] +#![feature(extern_item_impls)] + +#[eii(eii1)] +fn decl1(x: u64) {} diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.rs b/tests/ui/eii/duplicate/dylib_default_duplicate.rs new file mode 100644 index 0000000000000..0ac3669715f85 --- /dev/null +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.rs @@ -0,0 +1,20 @@ +//@ aux-build: dylib_default.rs +//@ needs-crate-type: dylib +//@ compile-flags: --emit link +//@ ignore-backends: gcc +// FIXME: linking on windows (specifically mingw) not yet supported, see tracking issue #125418 +//@ ignore-windows +// Regression test for https://github.com/rust-lang/rust/issues/156320. +// A default implementation from an upstream dylib has already been selected and +// must not be overridden by a downstream explicit implementation. +#![feature(extern_item_impls)] + +extern crate dylib_default; + +#[unsafe(dylib_default::eii1)] +fn other(x: u64) { + //~^ ERROR multiple implementations of `#[eii1]` + println!("1{x}"); +} + +fn main() {} diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.stderr b/tests/ui/eii/duplicate/dylib_default_duplicate.stderr new file mode 100644 index 0000000000000..04c6a13710e28 --- /dev/null +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.stderr @@ -0,0 +1,15 @@ +error: multiple implementations of `#[eii1]` + --> $DIR/dylib_default_duplicate.rs:15:1 + | +LL | fn other(x: u64) { + | ^^^^^^^^^^^^^^^^ first implemented here in crate `dylib_default_duplicate` + | + ::: $DIR/auxiliary/dylib_default.rs:5:1 + | +LL | fn decl1(x: u64) {} + | ---------------- also implemented here in crate `dylib_default` + | + = help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + +error: aborting due to 1 previous error + From 53897bb1f15f10cdd4109e1f4856e1491103ab55 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Thu, 28 May 2026 13:24:47 -0400 Subject: [PATCH 21/78] address a few nits --- compiler/rustc_codegen_ssa/src/back/link.rs | 23 ++++++++++----------- compiler/rustc_codegen_ssa/src/base.rs | 5 ++--- compiler/rustc_codegen_ssa/src/lib.rs | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 587515b84a587..74631030e9b09 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -82,9 +82,9 @@ fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol { } fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) { - let Some(eii_linkage) = &crate_info.eii_linkage else { + if crate_info.eii_linkage.is_empty() { return; - }; + } // A crate can request multiple linked outputs with overlapping dependency // formats, so report each underlying conflict once. @@ -93,7 +93,7 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat // This needs the dependency formats selected for the final artifact. The // earlier EII pass still handles missing impls and duplicate explicit impls. for dependency_formats in crate_info.dependency_formats.values() { - for (eii_index, eii) in eii_linkage.iter().enumerate() { + for (eii_index, eii) in crate_info.eii_linkage.iter().enumerate() { let mut explicit_impls = eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { @@ -159,7 +159,14 @@ pub fn link_binary( let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); let mut tempfiles_for_stdout_output: Vec = Vec::new(); let mut rmeta_link_cache = RmetaLinkCache::default(); - let mut checked_eii_linkage = false; + + if outputs.outputs.should_link() { + sess.time("check_externally_implementable_item_linkage", || { + check_externally_implementable_item_linkage(sess, &crate_info); + }); + sess.dcx().abort_if_errors(); + } + for &crate_type in &crate_info.crate_types { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) @@ -185,14 +192,6 @@ pub fn link_binary( }); if outputs.outputs.should_link() { - if !checked_eii_linkage { - sess.time("check_externally_implementable_item_linkage", || { - check_externally_implementable_item_linkage(sess, &crate_info); - }); - sess.dcx().abort_if_errors(); - checked_eii_linkage = true; - } - let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name); let tmpdir = TempDirBuilder::new() .prefix("rustc") diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index b8b19721ef377..e35c446d609fc 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -973,10 +973,9 @@ impl CrateInfo { let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind); let dependency_formats = Arc::clone(tcx.dependency_formats(())); let eii_linkage = if eii_linkage_needed(&dependency_formats) { - let eii_linkage = collect_eii_linkage(tcx); - (!eii_linkage.is_empty()).then_some(eii_linkage) + collect_eii_linkage(tcx) } else { - None + Vec::new() }; // This list is used when generating the command line to pass through to diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 8de2092489e93..ee8542e4c3c33 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -296,7 +296,7 @@ pub struct CrateInfo { pub dependency_formats: Arc, /// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a /// later `-Zlink-only` invocation. - pub eii_linkage: Option>, + pub eii_linkage: Vec, pub windows_subsystem: Option, pub natvis_debugger_visualizers: BTreeSet, pub lint_level_specs: CodegenLintLevelSpecs, From f6ce3bacd1f682b3d8fb01d32d46875530cf0483 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Thu, 28 May 2026 18:21:44 -0400 Subject: [PATCH 22/78] preserve EII rlib-only aux builds --- tests/ui/eii/default/auxiliary/decl_with_default.rs | 1 + tests/ui/eii/default/auxiliary/impl1.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/eii/default/auxiliary/decl_with_default.rs b/tests/ui/eii/default/auxiliary/decl_with_default.rs index ba855cb854afd..8d962c19c94d9 100644 --- a/tests/ui/eii/default/auxiliary/decl_with_default.rs +++ b/tests/ui/eii/default/auxiliary/decl_with_default.rs @@ -1,3 +1,4 @@ +//@ no-prefer-dynamic #![crate_type = "rlib"] #![feature(extern_item_impls)] diff --git a/tests/ui/eii/default/auxiliary/impl1.rs b/tests/ui/eii/default/auxiliary/impl1.rs index 84edf24e12816..3510ea1eb3f27 100644 --- a/tests/ui/eii/default/auxiliary/impl1.rs +++ b/tests/ui/eii/default/auxiliary/impl1.rs @@ -1,3 +1,4 @@ +//@ no-prefer-dynamic //@ aux-build: decl_with_default.rs #![crate_type = "rlib"] #![feature(extern_item_impls)] From ae82b10c3605cf01208f4f80503be26917974682 Mon Sep 17 00:00:00 2001 From: Alejandra Gonzalez Date: Mon, 6 Jul 2026 19:26:57 +0200 Subject: [PATCH 23/78] Improve error message on `-Zls`, to direct the user to input .rmeta files instead of .rs files --- compiler/rustc_driver_impl/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 31353d1eac0fb..cf1271878a316 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -597,6 +597,16 @@ fn process_rlink(sess: &Session, compiler: &interface::Compiler) { fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { match sess.io.input { Input::File(ref path) => { + if path.extension().is_some_and(|extension| extension == "rs") { + let mut err = sess + .dcx() + .struct_fatal("`-Zls` takes a `.rmeta` file as input, not a source file"); + if rustc_session::utils::was_invoked_from_cargo() { + // Give a Cargo-tailored suggestion if we're coming from Cargo + err.note("use `rustc +nightly -Zls=... path/to/file.rmeta` directly, instead of going through Cargo"); + } + err.emit(); + } let mut v = Vec::new(); locator::list_file_metadata( &sess.target, From a6b888c2b9fbdb2da408b123252772ae1d4c46cc Mon Sep 17 00:00:00 2001 From: Alejandra Gonzalez Date: Mon, 6 Jul 2026 19:53:30 +0200 Subject: [PATCH 24/78] Update Unstable Book with some information about `-Zls` --- src/doc/unstable-book/src/compiler-flags/ls.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/doc/unstable-book/src/compiler-flags/ls.md diff --git a/src/doc/unstable-book/src/compiler-flags/ls.md b/src/doc/unstable-book/src/compiler-flags/ls.md new file mode 100644 index 0000000000000..c0ad80d6e86e6 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/ls.md @@ -0,0 +1,17 @@ +# `ls` + +--- + +Option `-Zls` instructs the compiler to list all metadata from a given metadata file (i.e. files with the `.rmeta` extension). + +This allows for debugging the metadata emitted by an earlier compilation. + +Note that, while `rustc` usually works with `.rs` files, this option is meant purely for analyzing `.rmeta` files, and does not produce any compilation artifact. + +## Example + +```sh +rustc +nightly -Zls=all target/debug/deps/libmy_crate-*.rmeta +``` + +This lists to stdout all metadata from the given `.rmeta` file From 6741efa81442bf638b240acfcb0886d54be2ac91 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Sat, 27 Jun 2026 08:45:43 +0200 Subject: [PATCH 25/78] borrowck: Keep returned `path` from `best_blame_constraint()` consistent The function `fn best_blame_constraint()` returns a `Vec` and a `BlameConstraint`. The `BlameConstraint` is really just a slimmed down version of an `OutlivesConstraint`. So we can remove `BlameConstraint` entirely and instead return `Vec` and an index into that `Vec`. (Encapsulated in a struct for nice ergonomics.) To prepare for that, change the code to adjust `Vec` in-place and return info about the adjusted element, rather than returning info for a newly created "fake" `OutlivesConstraint`. --- compiler/rustc_borrowck/src/region_infer/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 64aa92c2c8878..505aa74b04437 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1630,7 +1630,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { }); // Edge case: it's possible that `'from_region` is an unnameable placeholder. - let path = if let Some(unnameable) = due_to_placeholder_outlives + let mut path = if let Some(unnameable) = due_to_placeholder_outlives && unnameable != from_region { // We ignore the extra edges due to unnameable placeholders to get @@ -1799,10 +1799,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { if let ConstraintCategory::ClosureUpvar(f) = p.category { Some(f) } else { None } }) { - OutlivesConstraint { - category: ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)), - ..path[best_choice] - } + path[best_choice].category = + ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)); + path[best_choice] } else { path[best_choice] }; From 2d18054569d1169bfae7ce21e5c7111f32f16774 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Mon, 6 Jul 2026 15:48:13 -0400 Subject: [PATCH 26/78] tests: fix enum-match.rs to handle LLVM 23 A recent change in InstSimplify is able to skip the `and` in this codegen, which is strictly an improvement. We accept the new version on newer LLVM and the old verison on older ones using the revisions system. The change in array-cmp.rs appears to have come from the same revision, so I gave it the same treatment. It's weirder to me though, because it merely changes the order of the phi operands which if I understand right doesn't actually matter. --- tests/codegen-llvm/array-cmp.rs | 6 +++++- tests/codegen-llvm/enum/enum-match.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/codegen-llvm/array-cmp.rs b/tests/codegen-llvm/array-cmp.rs index 0106b9c15c11b..5b0a802f4097e 100644 --- a/tests/codegen-llvm/array-cmp.rs +++ b/tests/codegen-llvm/array-cmp.rs @@ -2,6 +2,9 @@ //@ compile-flags: -C opt-level=2 //@ needs-deterministic-layouts (checks depend on tuple layout) +//@ revisions: LLVM22 LLVM23 +//@ [LLVM22] max-llvm-major-version: 22 +//@ [LLVM23] min-llvm-version: 23 #![crate_type = "lib"] @@ -66,7 +69,8 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK-NEXT: br i1 %[[EQ01]], label %{{.+}}, label %[[EXIT_U]] // CHECK: [[DONE]]: - // CHECK: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_S]] ], [ %{{.+}}, %[[EXIT_U]] ], [ true, %[[L11]] ] + // LLVM22: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_S]] ], [ %{{.+}}, %[[EXIT_U]] ], [ true, %[[L11]] ] + // LLVM23: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_U]] ], [ %{{.+}}, %[[EXIT_S]] ], [ true, %[[L11]] ] // CHECK: ret i1 %[[RET]] a <= b diff --git a/tests/codegen-llvm/enum/enum-match.rs b/tests/codegen-llvm/enum/enum-match.rs index 6d8b97328f8e0..a0cba452e123c 100644 --- a/tests/codegen-llvm/enum/enum-match.rs +++ b/tests/codegen-llvm/enum/enum-match.rs @@ -1,5 +1,8 @@ //@ compile-flags: -Copt-level=1 //@ only-64bit +//@ revisions: LLVM22 LLVM23 +//@ [LLVM22] max-llvm-major-version: 22 +//@ [LLVM23] min-llvm-version: 23 #![crate_type = "lib"] #![feature(core_intrinsics)] @@ -18,8 +21,9 @@ pub enum Enum0 { // CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match0(i8{{.+}}%0) // CHECK-NEXT: start: // CHECK-NEXT: %[[IS_B:.+]] = icmp eq i8 %0, 2 -// CHECK-NEXT: %[[TRUNC:.+]] = and i8 %0, 1 -// CHECK-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %[[TRUNC]] +// LLVM22-NEXT: %[[TRUNC:.+]] = and i8 %0, 1 +// LLVM22-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %[[TRUNC]] +// LLVM23-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %0 // CHECK-NEXT: ret i8 %[[R]] #[no_mangle] pub fn match0(e: Enum0) -> u8 { From 60502940cf02143fc3f185985349f531c8dab565 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 1 Jul 2026 15:02:45 +1000 Subject: [PATCH 27/78] Split unsafe fn ptr accesses into their own test --- tests/ui/splat/splat-fn-ptr-ptr-tuple.rs | 42 ++++++++++++++++++++ tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr | 24 +++++++++++ tests/ui/splat/splat-fn-ptr-tuple.rs | 20 ++-------- 3 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 tests/ui/splat/splat-fn-ptr-ptr-tuple.rs create mode 100644 tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs new file mode 100644 index 0000000000000..4663f0a96cc88 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs @@ -0,0 +1,42 @@ +//! Test using `#[splat]` on tuple arguments of pointers to pointers to simple functions. +//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. + +//@ failure-status: 101 + +//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" +//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" +//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" +//@ normalize-stderr: " +\d{1,}: .*\n" -> "" +//@ normalize-stderr: " + at .*\n" -> "" +//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" +//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" +//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" + +#![allow(incomplete_features)] +#![feature(splat)] + +fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in + // MIR lowering + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + let fn_pp: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); + unsafe { + (*fn_pp)(1, 2); //~ ERROR no splatted def for function or method callee + // The ICE means that code after this line is not fully checked + (*fn_pp)(1u32, 2i8); + } + + #[rustfmt::skip] + let fn_pp: *const fn(#[splat] (u32, i8), f64) = + splat_non_terminal_arg as *const fn(#[splat] (u32, i8), f64); + unsafe { + (*fn_pp)(1, 2, 3.5); + (*fn_pp)(1u32, 2i8, 3.5f64); + } +} diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr new file mode 100644 index 0000000000000..2674ce3f25f4a --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr @@ -0,0 +1,24 @@ +error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-ptr-tuple.rs:30:9 + | +LL | (*fn_pp)(1, 2); + | ^^^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main` +#1 [check_unsafety] unsafety-checking `main` +#2 [analysis] running analysis passes on crate `splat_fn_ptr_ptr_tuple` +end of query stack +error: aborting due to 1 previous error + diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index 297dbc0457794..d939c296726f9 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -1,3 +1,6 @@ +//! Test using `#[splat]` on tuple arguments of pointers to simple functions. +//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. + //@ failure-status: 101 //@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" @@ -10,9 +13,6 @@ //@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" //@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" -//! Test using `#[splat]` on tuple arguments of simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. - #![allow(incomplete_features)] #![feature(splat)] @@ -24,10 +24,10 @@ fn main() { // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in // MIR lowering // FIXME(rustfmt): the attribute gets deleted by rustfmt - // Functions #[rustfmt::skip] let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args; fn_ptr(1, 2); //~ ERROR no splatted def for function or method callee + // The ICE means that code after this line is not fully checked fn_ptr(1u32, 2i8); // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? @@ -38,16 +38,4 @@ fn main() { let fn_ptr: fn(#[splat] (u32, i8), f64) = splat_non_terminal_arg; fn_ptr(1, 2, 3.5); fn_ptr(1u32, 2i8, 3.5f64); - - // Function pointers - #[rustfmt::skip] - let fn_ptr: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); - (*fn_ptr)(1, 2); - (*fn_ptr)(1u32, 2i8); - - #[rustfmt::skip] - let fn_ptr: *const fn(#[splat] (u32, i8), f64) = - splat_non_terminal_arg as *const fn(#[splat] (u32, i8), f64); - (*fn_ptr)(1, 2, 3.5); - (*fn_ptr)(1u32, 2i8, 3.5f64); } From 0b6190fd46e817660a4fe118480a34c392e1a016 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 1 Jul 2026 13:21:28 +1000 Subject: [PATCH 28/78] Check arg errors then write splatted call info --- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 26 +++++++++---------- tests/ui/splat/arg-count-ice-issue-158482.rs | 14 ++++++++++ .../splat/arg-count-ice-issue-158482.stderr | 9 +++++++ 3 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 tests/ui/splat/arg-count-ice-issue-158482.rs create mode 100644 tests/ui/splat/arg-count-ice-issue-158482.stderr diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 7d85a5a4ded63..045bc03c918f9 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -702,19 +702,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { expected_input_tys = None; } - // If splatting, record this call in a side-table, so MIR lowering can tuple the caller's arguments - if tuple_arguments.is_splatted() { - // FIXME(const_trait_impl): does not enforce constness yet - self.write_splatted_call( - call_expr.hir_id, - call_span, - fn_def_id, - callee_generic_args, - first_tupled_arg_index.try_into().unwrap(), - tupled_args_count.unwrap().try_into().unwrap(), - ); - } - formal_input_tys.splice( first_tupled_arg_index..=first_tupled_arg_index, detup_formal_arg_tys.iter(), @@ -803,6 +790,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { untupled_expected_input_tys: None, } } else { + // If splatting, record this call in a side-table, so MIR lowering can tuple the caller's arguments + if tuple_arguments.is_splatted() { + // FIXME(const_trait_impl): does not enforce constness yet + self.write_splatted_call( + call_expr.hir_id, + call_span, + fn_def_id, + callee_generic_args, + first_tupled_arg_index.try_into().unwrap(), + tupled_args_count.unwrap().try_into().unwrap(), + ); + } + TupledArgCheckOutcome { new_err_code: err_code, untupled_formal_input_tys: formal_input_tys, diff --git a/tests/ui/splat/arg-count-ice-issue-158482.rs b/tests/ui/splat/arg-count-ice-issue-158482.rs new file mode 100644 index 0000000000000..27f036b8a5906 --- /dev/null +++ b/tests/ui/splat/arg-count-ice-issue-158482.rs @@ -0,0 +1,14 @@ +//! Checks that an incorrect number of arguments to splat doesn't panic. + +#![feature(splat)] +struct Foo {} +trait BarTrait { + fn trait_assoc(w: W, #[splat] _s: (u32, u8)); +} +impl BarTrait for Foo { + fn trait_assoc(_w: W, #[splat] _s: (u32, u8)) {} +} +fn main() { + Foo::trait_assoc() + //~^ ERROR: this splatted function takes 3 arguments, but 0 were provided +} diff --git a/tests/ui/splat/arg-count-ice-issue-158482.stderr b/tests/ui/splat/arg-count-ice-issue-158482.stderr new file mode 100644 index 0000000000000..b7f3a8070f2b2 --- /dev/null +++ b/tests/ui/splat/arg-count-ice-issue-158482.stderr @@ -0,0 +1,9 @@ +error[E0057]: this splatted function takes 3 arguments, but 0 were provided + --> $DIR/arg-count-ice-issue-158482.rs:12:5 + | +LL | Foo::trait_assoc() + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0057`. From 23eeafc90f3b947934dd32a8c69a7e193192b35a Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 1 Jul 2026 13:35:32 +1000 Subject: [PATCH 29/78] Avoid an unwrap using a u16 splat index --- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 28 +++++++++++-------- compiler/rustc_hir_typeck/src/lib.rs | 4 +-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 045bc03c918f9..58472d2dc5bdd 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -592,6 +592,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { untupled_expected_input_tys: expected_input_tys, }; }; + let first_tupled_arg_index_usz = usize::from(first_tupled_arg_index); // The argument difference can range from -1 to u16::MAX - 1, so we count the number // of tupled arguments instead. @@ -610,7 +611,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If earlier code has modified the FnSig argument list without adjusting the splatted // argument, indexing into the formal input types will panic. - if first_tupled_arg_index >= formal_input_tys.len() { + if first_tupled_arg_index_usz >= formal_input_tys.len() { span_bug!( call_span, "splatted argument index is out of bounds: {first_tupled_arg_index:?} >= {}, \ @@ -622,10 +623,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } + let formal_input_tupled_ty = formal_input_tys[first_tupled_arg_index_usz]; // Keep the type variable if the argument is splatted, so we can force it to be a tuple later. let tuple_type = if tuple_arguments.is_splatted() { - let callee_tuple_type = - self.resolve_vars_with_obligations(formal_input_tys[first_tupled_arg_index]); + let callee_tuple_type = self.resolve_vars_with_obligations(formal_input_tupled_ty); if callee_tuple_type.is_ty_var() && let Some(tupled_args_count) = tupled_args_count { @@ -674,7 +675,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { callee_tuple_type } } else { - self.structurally_resolve_type(call_span, formal_input_tys[first_tupled_arg_index]) + self.structurally_resolve_type(call_span, formal_input_tupled_ty) }; // We expected a tuple and got a tuple (or made one ourselves). @@ -687,7 +688,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err_code = Some(E0057); } if let Some(ref mut expected_input_tys) = expected_input_tys - && let Some(ty) = expected_input_tys.get(first_tupled_arg_index) + && let Some(ty) = expected_input_tys.get(first_tupled_arg_index_usz) && let ty::Tuple(detup_expected_arg_tys) = ty.kind() { let substitute_tys = if Some(detup_expected_arg_tys.len()) == tupled_args_count { @@ -697,13 +698,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { detup_formal_arg_tys.iter() }; - expected_input_tys - .splice(first_tupled_arg_index..=first_tupled_arg_index, substitute_tys); + expected_input_tys.splice( + first_tupled_arg_index_usz..=first_tupled_arg_index_usz, + substitute_tys, + ); } else { expected_input_tys = None; } formal_input_tys.splice( - first_tupled_arg_index..=first_tupled_arg_index, + first_tupled_arg_index_usz..=first_tupled_arg_index_usz, detup_formal_arg_tys.iter(), ); if let Some(ref expected_input_tys) = expected_input_tys { @@ -711,7 +714,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { formal_input_tys.len(), expected_input_tys.len(), "incorrectly constructed input type tuples, argument counts must match: \ - tuple_arguments: {tuple_arguments:?}", + tuple_arguments: {tuple_arguments:?}, \ + first_tupled_arg_index: {first_tupled_arg_index}", ) } } @@ -738,7 +742,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let spans = if let Some(def_id) = fn_def_id && let Some(hir_node) = self.tcx.hir_get_if_local(def_id) && let Some(fn_decl) = hir_node.fn_decl() - && let Some(arg_ty) = fn_decl.inputs.get(first_tupled_arg_index) + && let Some(arg_ty) = fn_decl.inputs.get(first_tupled_arg_index_usz) { let arg_def_span = arg_ty.span; vec![call_span, arg_def_span] @@ -755,7 +759,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tuple_type.kind(), self.structurally_resolve_type( call_span, - formal_input_tys[first_tupled_arg_index] + formal_input_tys[first_tupled_arg_index_usz] ) .kind(), ) @@ -798,7 +802,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { call_span, fn_def_id, callee_generic_args, - first_tupled_arg_index.try_into().unwrap(), + first_tupled_arg_index, tupled_args_count.unwrap().try_into().unwrap(), ); } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 335caf571aa6f..db753017f63d9 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -666,9 +666,9 @@ impl TupleArgumentsFlag { /// Returns the tupled argument index, and whether the `self` argument is splatted. /// Returns `None` if the arguments are not tupled, or if the `self` argument is splatted. - fn tupled_arg_index(self) -> (Option, bool /* is_self_splatted */) { + fn tupled_arg_index(self) -> (Option, bool /* is_self_splatted */) { match self { - Self::TupleSplattedArg(index) => (Some(usize::from(index)), false), + Self::TupleSplattedArg(index) => (Some(u16::from(index)), false), Self::TupleAllCallArgs => (Some(0), false), Self::TupleSplattedSelfArg => (None, true), Self::DontTupleArguments => (None, false), From 1e3fca804566455b62b6a645f20bc28250e301e3 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 1 Jul 2026 14:24:16 +1000 Subject: [PATCH 30/78] Remove a splat check arg only used for debugging --- compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 58472d2dc5bdd..4321251f23ca2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -299,7 +299,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { formal_input_tys, provided_args, expected_input_tys, - c_variadic, tuple_arguments, fn_def_id, callee_generic_args, @@ -573,10 +572,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args: &'tcx [hir::Expr<'tcx>], // The expected input types from the context of the call site mut expected_input_tys: Option>>, - // Whether the function is variadic (e.g. from C) - c_variadic: bool, - // Whether all the arguments have been bundled in a tuple (ex: closures). - // Splatting is handled separately. + // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, // The DefId for the function being called, for better error messages fn_def_id: Option, @@ -605,7 +601,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let tupled_args_count = (1 + provided_args.len()).checked_sub(formal_input_tys.len()); debug!( ?first_tupled_arg_index, ?is_self_splatted, - ?tupled_args_count, ?tuple_arguments, ?c_variadic, + ?tupled_args_count, ?tuple_arguments, provided_args_len = ?provided_args.len(), formal_input_tys_len = ?formal_input_tys.len() ); @@ -617,7 +613,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "splatted argument index is out of bounds: {first_tupled_arg_index:?} >= {}, \ is_self_splatted = {is_self_splatted:?}, \ tupled_args_count = {tupled_args_count:?}, {tuple_arguments:?}, \ - c_variadic = {c_variadic:?}, provided_args: {}", + provided_args: {}", formal_input_tys.len(), provided_args.len(), ); From baccb0c7d67748df3f27ff5359cec84a47116838 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 1 Jul 2026 15:04:36 +1000 Subject: [PATCH 31/78] Improve ICE diag for splatted FnPtrs --- compiler/rustc_mir_build/src/thir/cx/expr.rs | 52 ++++++++++++++----- tests/ui/splat/splat-fn-ptr-cast.rs | 17 ++++++ tests/ui/splat/splat-fn-ptr-cast.stderr | 10 ++++ tests/ui/splat/splat-fn-ptr-ptr-tuple.rs | 2 +- tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr | 2 +- tests/ui/splat/splat-fn-ptr-tuple-const.rs | 34 ++++++++++++ .../ui/splat/splat-fn-ptr-tuple-const.stderr | 52 +++++++++++++++++++ tests/ui/splat/splat-fn-ptr-tuple.rs | 7 ++- tests/ui/splat/splat-fn-ptr-tuple.stderr | 2 +- 9 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 tests/ui/splat/splat-fn-ptr-cast.rs create mode 100644 tests/ui/splat/splat-fn-ptr-cast.stderr create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-const.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-const.stderr diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index c286c0819aea2..650dcf61c66ac 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1225,26 +1225,50 @@ impl<'tcx> ThirBuildCx<'tcx> { self.typeck_results.splatted_def(expr.hir_id).unwrap_or_else(|| { span_bug!(expr.span, "no splatted def for function or method callee") }); - let def_id = def_id.unwrap_or_else(|| { - span_bug!(expr.span, "no splatted def for function or method callee") - }); - let def_kind = self.tcx.def_kind(def_id); - let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); - debug!( - "splatted_callee: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", - user_ty, def_kind, def_id, arg_index, arg_count - ); - ( + let expr = if let Some(def_id) = def_id { + // We're calling a function via a FnDef, and its possibly generic type + let def_kind = self.tcx.def_kind(def_id); + let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count, + ); + Expr { temp_scope_id: expr.hir_id.local_id, + // Create a new FnDef type, representing the splatted function arguments with + // user-supplied generic types applied ty: Ty::new_fn_def(self.tcx, def_id, self.typeck_results.node_args(expr.hir_id)), span, kind: ExprKind::ZstLiteral { user_ty }, - }, - arg_index, - arg_count, - ) + } + } else { + // We're calling a function via a FnPtr and its type + // FIXME(splat): populate the side-tables for FnPtrs, using liberated_fn_sigs if needed + let fn_ty = self.typeck_results.expr_ty_adjusted(expr); + let user_ty = + self.typeck_results.user_provided_types().get(expr.hir_id).copied().map(Box::new); + debug!( + "splatted_callee FnPtr: user_ty={:?} fn_ty={:?} arg_index={:?} arg_count={:?}", + user_ty, fn_ty, arg_index, arg_count, + ); + + if !fn_ty.is_fn() { + span_bug!(expr.span, "splatted FnPtr side-tables are not yet implemented") + } + + Expr { + temp_scope_id: expr.hir_id.local_id, + // Create a new FnPtr FnSig type, representing the splatted function arguments with + // user-supplied generic types applied + ty: Ty::new_fn_ptr(self.tcx, fn_ty.fn_sig(self.tcx)), + span, + kind: ExprKind::ZstLiteral { user_ty }, + } + }; + + (expr, arg_index, arg_count) } /// The callee has a splatted tuple argument. diff --git a/tests/ui/splat/splat-fn-ptr-cast.rs b/tests/ui/splat/splat-fn-ptr-cast.rs new file mode 100644 index 0000000000000..1560894caca7d --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-cast.rs @@ -0,0 +1,17 @@ +//@ run-fail +//! Test never type casts to splatted and non-splatted functions. + +#![allow(incomplete_features)] +#![feature(splat)] +#![allow(unused_features)] + +fn main() { + // Bug #158603 regression test variants + #[rustfmt::skip] + let _x: fn(#[splat] (i32,)) = None.unwrap(); + // FIXME(splat): causes an ICE until #158603 is fixed + //x(1); + + let x: fn((i32,)) = None.unwrap(); + x((1,)); +} diff --git a/tests/ui/splat/splat-fn-ptr-cast.stderr b/tests/ui/splat/splat-fn-ptr-cast.stderr new file mode 100644 index 0000000000000..040007f6b9cd8 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-cast.stderr @@ -0,0 +1,10 @@ +warning: feature `splat` is declared but not used + --> $DIR/splat-fn-ptr-cast.rs:5:12 + | +LL | #![feature(splat)] + | ^^^^^ + | + = note: `#[warn(unused_features)]` (part of `#[warn(unused)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs index 4663f0a96cc88..321511f270915 100644 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs @@ -27,7 +27,7 @@ fn main() { #[rustfmt::skip] let fn_pp: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); unsafe { - (*fn_pp)(1, 2); //~ ERROR no splatted def for function or method callee + (*fn_pp)(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented // The ICE means that code after this line is not fully checked (*fn_pp)(1u32, 2i8); } diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr index 2674ce3f25f4a..726561b366f80 100644 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr @@ -1,4 +1,4 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: no splatted def for function or method callee +error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented --> $DIR/splat-fn-ptr-ptr-tuple.rs:30:9 | LL | (*fn_pp)(1, 2); diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.rs b/tests/ui/splat/splat-fn-ptr-tuple-const.rs new file mode 100644 index 0000000000000..faf8501cc650a --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.rs @@ -0,0 +1,34 @@ +//! Test using `#[splat]` on tuple arguments of generic function constants. +//! Currently ICEs (#158603), but if we fix it, we'll want to know and update this test to pass. + +//@ failure-status: 101 + +//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" +//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" +//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" +//@ normalize-stderr: " +\d{1,}: .*\n" -> "" +//@ normalize-stderr: " + at .*\n" -> "" +//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" +//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" +//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" + +#![allow(incomplete_features)] +#![feature(splat, tuple_trait)] + +use std::marker::Tuple; + +fn f(#[splat] args: Args) {} + +fn main() { + // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in + // MIR lowering + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + const F2: fn(#[splat] (u8, u32)) = f::<(u8, u32)>; + const R2: () = F2(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented + + #[rustfmt::skip] + const F1: fn(#[splat] ((u8, u32),)) = f::<((u8, u32),)>; + const R1: () = F1((1, 2)); //~ ERROR splatted FnPtr side-tables are not yet implemented +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr new file mode 100644 index 0000000000000..1767782a9535e --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr @@ -0,0 +1,52 @@ +error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented + --> $DIR/splat-fn-ptr-tuple-const.rs:29:20 + | +LL | const R2: () = F2(1, 2); + | ^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main::R2` +#1 [check_match] match-checking `main::R2` +#2 [mir_built] building MIR for `main::R2` +#3 [trivial_const] checking if `main::R2` is a trivial const +#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R2` +#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` +end of query stack +error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented + --> $DIR/splat-fn-ptr-tuple-const.rs:33:20 + | +LL | const R1: () = F1((1, 2)); + | ^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main::R1` +#1 [check_match] match-checking `main::R1` +#2 [mir_built] building MIR for `main::R1` +#3 [trivial_const] checking if `main::R1` is a trivial const +#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R1` +#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` +end of query stack +error: aborting due to 2 previous errors + diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index d939c296726f9..395d30baedaaf 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -26,7 +26,7 @@ fn main() { // FIXME(rustfmt): the attribute gets deleted by rustfmt #[rustfmt::skip] let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args; - fn_ptr(1, 2); //~ ERROR no splatted def for function or method callee + fn_ptr(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented // The ICE means that code after this line is not fully checked fn_ptr(1u32, 2i8); @@ -38,4 +38,9 @@ fn main() { let fn_ptr: fn(#[splat] (u32, i8), f64) = splat_non_terminal_arg; fn_ptr(1, 2, 3.5); fn_ptr(1u32, 2i8, 3.5f64); + + // Bug #158603 regression test + #[rustfmt::skip] + let x: fn(#[splat] (i32,)) = None.unwrap(); + x(1); } diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr index a69175bd5e886..4cc861cafe968 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple.stderr @@ -1,4 +1,4 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: no splatted def for function or method callee +error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented | LL | fn_ptr(1, 2); | ^^^^^^^^^^^^ From 6a68767d968bfce5419535b8be391656eddda132 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 1 Jul 2026 17:24:21 +1000 Subject: [PATCH 32/78] Ban splat on closures and rust-call --- .../rustc_ast_passes/src/ast_validation.rs | 138 ++++++++++++++---- compiler/rustc_ast_passes/src/diagnostics.rs | 33 ++++- compiler/rustc_span/src/symbol.rs | 1 + tests/ui/splat/reject-closure-issue-158605.rs | 30 ++++ .../splat/reject-closure-issue-158605.stderr | 36 +++++ tests/ui/splat/splat-255-limit-fail.rs | 6 +- tests/ui/splat/splat-255-limit-fail.stderr | 16 +- tests/ui/splat/splat-async-fn-tuple-fail.rs | 2 +- .../ui/splat/splat-async-fn-tuple-fail.stderr | 4 +- tests/ui/splat/splat-fn-ptr-cast.rs | 4 +- tests/ui/splat/splat-fn-ptr-cast.stderr | 10 -- tests/ui/splat/splat-fn-ptr-rust-call.rs | 18 +++ tests/ui/splat/splat-fn-ptr-rust-call.stderr | 30 ++++ tests/ui/splat/splat-invalid.rs | 26 +++- tests/ui/splat/splat-invalid.stderr | 88 +++++++---- tests/ui/splat/splat-unsafe-fn-tuple-fail.rs | 2 +- .../splat/splat-unsafe-fn-tuple-fail.stderr | 4 +- 17 files changed, 355 insertions(+), 93 deletions(-) create mode 100644 tests/ui/splat/reject-closure-issue-158605.rs create mode 100644 tests/ui/splat/reject-closure-issue-158605.stderr delete mode 100644 tests/ui/splat/splat-fn-ptr-cast.stderr create mode 100644 tests/ui/splat/splat-fn-ptr-rust-call.rs create mode 100644 tests/ui/splat/splat-fn-ptr-rust-call.stderr diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 06925994b052c..6a689422f2956 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -16,6 +16,7 @@ //! constructions produced by proc macros. This pass is only intended for simple checks that do not //! require name resolution or type checking, or other kinds of complex analysis. +use std::collections::BTreeMap; use std::mem; use std::str::FromStr; @@ -34,7 +35,7 @@ use rustc_session::lint::builtin::{ DEPRECATED_WHERE_CLAUSE_LOCATION, MISSING_ABI, MISSING_UNSAFE_ON_EXTERN, PATTERNS_IN_FNS_WITHOUT_BODY, UNUSED_VISIBILITIES, }; -use rustc_span::{Ident, Span, kw, sym}; +use rustc_span::{Ident, Span, Symbol, kw, sym}; use rustc_target::spec::{AbiMap, AbiMapping}; use crate::diagnostics::{self, TildeConstReason}; @@ -45,6 +46,41 @@ enum SelfSemantic { No, } +/// Is `#[splat]` allowed semantically in a function or closure? +/// Only applies to the function kind and header, the parameters are checked elsewhere. +enum SplatSemantic { + Yes, + NoClosures(Span), + NoAbiCall { span: Span, abi: Symbol }, +} + +impl SplatSemantic { + /// Returns if splatting is semantically allowed for the given `FnKind`, + /// Only checks the function kind and header, not the parameters. + fn from_fn_kind(fk: &FnKind<'_>) -> Self { + match fk { + FnKind::Fn(_, _, f) => Self::from_extern(f.sig.header.ext), + // Splatting closures is banned, because closure arguments are already de-tupled. + FnKind::Closure(_, _, _, expr) => SplatSemantic::NoClosures(expr.span), + } + } + + fn from_extern(ext: Extern) -> Self { + match ext { + Extern::None => SplatSemantic::Yes, + // FIXME(splat): should splatting extern "C" or other ABIs be allowed? + Extern::Implicit(_) => SplatSemantic::Yes, + // For now, splatting rust-call is banned, because it already de-tuples args. + Extern::Explicit(abi_str, span) => match abi_str.symbol_unescaped { + sym::rust_dash_call => { + SplatSemantic::NoAbiCall { span, abi: abi_str.symbol_unescaped } + } + _ => SplatSemantic::Yes, + }, + } + } +} + enum TraitOrImpl { Trait { vis: Span, constness: Const }, TraitImpl { constness: Const, polarity: ImplPolarity, trait_ref_span: Span }, @@ -350,10 +386,15 @@ impl<'a> AstValidator<'a> { }); } - fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) { + fn check_fn_decl( + &self, + fn_decl: &FnDecl, + self_semantic: SelfSemantic, + splat_semantic: SplatSemantic, + ) { self.check_decl_num_args(fn_decl); let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl); - self.check_decl_splatting(fn_decl, c_variadic_span); + self.check_decl_splatting(fn_decl, c_variadic_span, splat_semantic); self.check_decl_attrs(fn_decl); self.check_decl_self_param(fn_decl, self_semantic); } @@ -399,42 +440,76 @@ impl<'a> AstValidator<'a> { /// Emits an error if a function declaration has more than one splatted argument, with a /// C-variadic parameter, or a splat at an unsupported index (for performance). /// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error. - fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option) { - let (splatted_arg_indexes, mut splatted_spans): (Vec, Vec) = fn_decl + fn check_decl_splatting( + &self, + fn_decl: &FnDecl, + c_variadic_span: Option, + splat_semantic: SplatSemantic, + ) { + let mut splatted_arg_spans: BTreeMap> = fn_decl .inputs .iter() .enumerate() .filter_map(|(index, arg)| { - arg.attrs + let splat_arg_spans: Vec = arg + .attrs .iter() - .any(|attr| attr.has_name(sym::splat)) - .then_some((u16::try_from(index).unwrap(), arg.span)) + .filter_map(|attr| attr.has_name(sym::splat).then_some(attr.span)) + .collect(); + if splat_arg_spans.is_empty() { + None + } else { + Some((u16::try_from(index).unwrap(), splat_arg_spans)) + } }) - .unzip(); + .collect(); // A splatted argument greater than or equal to the "no splatted" marker index is not - // supported. - if let (Some(&splatted_arg_index), Some(&splatted_span)) = - (splatted_arg_indexes.last(), splatted_spans.last()) - && splatted_arg_index >= u16::from(FnDecl::NO_SPLATTED_ARG_INDEX) - { - self.dcx().emit_err(diagnostics::InvalidSplattedArg { - splatted_arg_index, - span: splatted_span, + // supported. It is ok to drop these spans after issuing this error, because they are + // always invalid. + let out_of_range_spans = + splatted_arg_spans.split_off(&u16::from(FnDecl::NO_SPLATTED_ARG_INDEX)); + if !out_of_range_spans.is_empty() { + self.dcx().emit_err(diagnostics::InvalidSplattedArgs { + min_invalid_splatted_arg_index: u16::from(FnDecl::NO_SPLATTED_ARG_INDEX), + first_invalid_splatted_arg_index: *out_of_range_spans.keys().next().unwrap(), + spans: out_of_range_spans.values().flatten().copied().collect(), }); } - // Multiple splatted arguments are invalid: we can't know which arguments go in each splat. - if splatted_arg_indexes.len() > 1 { - self.dcx() - .emit_err(diagnostics::DuplicateSplattedArgs { spans: splatted_spans.clone() }); - } + if !splatted_arg_spans.is_empty() { + let splatted_spans = || splatted_arg_spans.values().flatten().copied().collect(); - if let Some(c_variadic_span) = c_variadic_span - && !splatted_spans.is_empty() - { - splatted_spans.push(c_variadic_span); - self.dcx().emit_err(diagnostics::CVarArgsAndSplat { spans: splatted_spans }); + // Multiple splatted arguments are invalid: we can't know which arguments go in each splat. + if splatted_arg_spans.len() > 1 { + self.dcx().emit_err(diagnostics::DuplicateSplattedArgs { spans: splatted_spans() }); + } + + // C-variadic parameters and splats are not allowed together. + if let Some(c_variadic_span) = c_variadic_span { + let mut splatted_spans = splatted_spans(); + splatted_spans.push(c_variadic_span); + self.dcx().emit_err(diagnostics::CVarArgsAndSplat { spans: splatted_spans }); + } + + // Splatting is not allowed on closures, or some function ABIs. + match splat_semantic { + SplatSemantic::NoClosures(closure_span) => { + let mut splatted_spans = splatted_spans(); + splatted_spans.push(closure_span); + self.dcx() + .emit_err(diagnostics::SplatNotAllowedOnClosures { spans: splatted_spans }); + } + SplatSemantic::NoAbiCall { span, abi } => { + let mut splatted_spans = splatted_spans(); + splatted_spans.push(span); + self.dcx().emit_err(diagnostics::SplatNotAllowedOnAbiCall { + spans: splatted_spans, + abi, + }); + } + SplatSemantic::Yes => {} + } } } @@ -1055,7 +1130,11 @@ impl<'a> AstValidator<'a> { match &ty.kind { TyKind::FnPtr(bfty) => { self.check_fn_ptr_safety(bfty.decl_span, bfty.safety); - self.check_fn_decl(&bfty.decl, SelfSemantic::No); + self.check_fn_decl( + &bfty.decl, + SelfSemantic::No, + SplatSemantic::from_extern(bfty.ext), + ); Self::check_decl_no_pat(&bfty.decl, |span, _, _| { self.dcx().emit_err(diagnostics::PatternFnPointer { span }); }); @@ -1746,7 +1825,8 @@ impl Visitor<'_> for AstValidator<'_> { Some(FnCtxt::Assoc(_)) => SelfSemantic::Yes, _ => SelfSemantic::No, }; - self.check_fn_decl(fk.decl(), self_semantic); + let splat_semantic = SplatSemantic::from_fn_kind(&fk); + self.check_fn_decl(fk.decl(), self_semantic, splat_semantic); if let Some(&FnHeader { safety, .. }) = fk.header() { self.check_item_safety(span, safety); diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 7a78e8e6213a5..90b82f102fd2d 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -124,18 +124,22 @@ pub(crate) struct FnParamCVarArgsNotLast { } #[derive(Diagnostic)] -#[diag("`#[splat]` is not supported on argument index {$splatted_arg_index}")] +#[diag( + "`#[splat]` is not supported on argument index {$min_invalid_splatted_arg_index} or greater, but is on index {$first_invalid_splatted_arg_index}" +)] #[help("remove `#[splat]`, or use it on an argument closer to the start of the argument list")] -pub(crate) struct InvalidSplattedArg { - pub splatted_arg_index: u16, +pub(crate) struct InvalidSplattedArgs { + pub min_invalid_splatted_arg_index: u16, + + pub first_invalid_splatted_arg_index: u16, #[primary_span] #[label("`#[splat]` is not supported here")] - pub span: Span, + pub spans: Vec, } #[derive(Diagnostic)] -#[diag("multiple `#[splat]`s are not allowed in the same function")] +#[diag("multiple `#[splat]`s are not allowed in the same function argument list")] #[help("remove `#[splat]` from all but one argument")] pub(crate) struct DuplicateSplattedArgs { #[primary_span] @@ -143,13 +147,30 @@ pub(crate) struct DuplicateSplattedArgs { } #[derive(Diagnostic)] -#[diag("`...` and `#[splat]` are not allowed in the same function")] +#[diag("`...` and `#[splat]` are not allowed in the same function argument list")] #[help("remove `#[splat]` or remove `...`")] pub(crate) struct CVarArgsAndSplat { #[primary_span] pub spans: Vec, } +#[derive(Diagnostic)] +#[diag("`#[splat]` is not allowed on closure arguments")] +#[help("remove `#[splat]` or turn the closure into a function")] +pub(crate) struct SplatNotAllowedOnClosures { + #[primary_span] + pub spans: Vec, +} + +#[derive(Diagnostic)] +#[diag("`#[splat]` is not allowed in the arguments of functions with the `{$abi}` ABI")] +#[help("remove `#[splat]` or change the ABI")] +pub(crate) struct SplatNotAllowedOnAbiCall { + #[primary_span] + pub spans: Vec, + pub abi: Symbol, +} + #[derive(Diagnostic)] #[diag("documentation comments cannot be applied to function parameters")] pub(crate) struct FnParamDocComment { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b99198d9ee8c4..f223279b7cd3f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1738,6 +1738,7 @@ symbols! { rust_analyzer, rust_begin_unwind, rust_cold_cc, + rust_dash_call: "rust-call", rust_eh_personality, rust_future, rust_logo, diff --git a/tests/ui/splat/reject-closure-issue-158605.rs b/tests/ui/splat/reject-closure-issue-158605.rs new file mode 100644 index 0000000000000..7464bab2a77d2 --- /dev/null +++ b/tests/ui/splat/reject-closure-issue-158605.rs @@ -0,0 +1,30 @@ +//! Checks that closures and rust-call functions can't be splatted. +//! This should be rejected until we decide on sensible semantics. + +#![feature(splat, unboxed_closures, tuple_trait)] +#![expect(incomplete_features)] + +use std::marker::Tuple; + +trait Trait: Tuple + Sized { + extern "rust-call" fn method(#[splat] self: Self); + //~^ ERROR: `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI +} + +impl Trait for (i32, i64) { + extern "rust-call" fn method(#[splat] self: Self) { + //~^ ERROR: `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + println!("{self:?}"); + } +} + +fn main() { + (|#[splat] x: i32| { + //~^ ERROR `#[splat]` is not allowed on closure arguments + println!("{x}"); + })(1); + + (1_i32, 2_i64).method(); + Trait::method(3_i32, 4_i64); + //~^ ERROR functions with the "rust-call" ABI must take a single non-self tuple argument +} diff --git a/tests/ui/splat/reject-closure-issue-158605.stderr b/tests/ui/splat/reject-closure-issue-158605.stderr new file mode 100644 index 0000000000000..c23211459a0db --- /dev/null +++ b/tests/ui/splat/reject-closure-issue-158605.stderr @@ -0,0 +1,36 @@ +error: `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + --> $DIR/reject-closure-issue-158605.rs:10:5 + | +LL | extern "rust-call" fn method(#[splat] self: Self); + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^ + | + = help: remove `#[splat]` or change the ABI + +error: `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + --> $DIR/reject-closure-issue-158605.rs:15:5 + | +LL | extern "rust-call" fn method(#[splat] self: Self) { + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^ + | + = help: remove `#[splat]` or change the ABI + +error: `#[splat]` is not allowed on closure arguments + --> $DIR/reject-closure-issue-158605.rs:22:7 + | +LL | (|#[splat] x: i32| { + | _______^^^^^^^^_________^ +LL | | +LL | | println!("{x}"); +LL | | })(1); + | |_____^ + | + = help: remove `#[splat]` or turn the closure into a function + +error: functions with the "rust-call" ABI must take a single non-self tuple argument + --> $DIR/reject-closure-issue-158605.rs:28:26 + | +LL | Trait::method(3_i32, 4_i64); + | ^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/splat/splat-255-limit-fail.rs b/tests/ui/splat/splat-255-limit-fail.rs index 31767f11d999b..77d9d84ec30da 100644 --- a/tests/ui/splat/splat-255-limit-fail.rs +++ b/tests/ui/splat/splat-255-limit-fail.rs @@ -68,7 +68,7 @@ fn s_256_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 256 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 or greater, but is on index 256 ) {} #[rustfmt::skip] @@ -88,7 +88,7 @@ fn s_255_non_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 or greater, but is on index 255 _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, ) {} @@ -110,7 +110,7 @@ fn s_256_non_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 256 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 or greater, but is on index 256 _: A, ) {} diff --git a/tests/ui/splat/splat-255-limit-fail.stderr b/tests/ui/splat/splat-255-limit-fail.stderr index da62da8aa605b..20801601930f1 100644 --- a/tests/ui/splat/splat-255-limit-fail.stderr +++ b/tests/ui/splat/splat-255-limit-fail.stderr @@ -1,32 +1,32 @@ -error: `#[splat]` is not supported on argument index 255 +error: `#[splat]` is not supported on argument index 255 or greater, but is on index 255 --> $DIR/splat-255-limit-fail.rs:50:5 | LL | #[splat] (_a, _b): (u32, i8), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | ^^^^^^^^ `#[splat]` is not supported here | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: `#[splat]` is not supported on argument index 256 +error: `#[splat]` is not supported on argument index 255 or greater, but is on index 256 --> $DIR/splat-255-limit-fail.rs:71:5 | LL | #[splat] (_a, _b): (u32, i8), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | ^^^^^^^^ `#[splat]` is not supported here | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: `#[splat]` is not supported on argument index 255 +error: `#[splat]` is not supported on argument index 255 or greater, but is on index 255 --> $DIR/splat-255-limit-fail.rs:91:5 | LL | #[splat] (_a, _b): (u32, i8), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | ^^^^^^^^ `#[splat]` is not supported here | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: `#[splat]` is not supported on argument index 256 +error: `#[splat]` is not supported on argument index 255 or greater, but is on index 256 --> $DIR/splat-255-limit-fail.rs:113:5 | LL | #[splat] (_a, _b): (u32, i8), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | ^^^^^^^^ `#[splat]` is not supported here | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list diff --git a/tests/ui/splat/splat-async-fn-tuple-fail.rs b/tests/ui/splat/splat-async-fn-tuple-fail.rs index a1f67dfe606d6..308a6c4a2131e 100644 --- a/tests/ui/splat/splat-async-fn-tuple-fail.rs +++ b/tests/ui/splat/splat-async-fn-tuple-fail.rs @@ -8,7 +8,7 @@ async fn async_wrong_type(#[splat] _x: u32) {} //~^ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 async fn async_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} -//~^ ERROR multiple `#[splat]`s are not allowed in the same function +//~^ ERROR multiple `#[splat]`s are not allowed in the same function argument list fn main() { async_wrong_type(1u32); diff --git a/tests/ui/splat/splat-async-fn-tuple-fail.stderr b/tests/ui/splat/splat-async-fn-tuple-fail.stderr index e643b29c88c8a..7c73382d73a5a 100644 --- a/tests/ui/splat/splat-async-fn-tuple-fail.stderr +++ b/tests/ui/splat/splat-async-fn-tuple-fail.stderr @@ -1,8 +1,8 @@ -error: multiple `#[splat]`s are not allowed in the same function +error: multiple `#[splat]`s are not allowed in the same function argument list --> $DIR/splat-async-fn-tuple-fail.rs:10:28 | LL | async fn async_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ ^^^^^^^^ | = help: remove `#[splat]` from all but one argument diff --git a/tests/ui/splat/splat-fn-ptr-cast.rs b/tests/ui/splat/splat-fn-ptr-cast.rs index 1560894caca7d..918e26c4dd741 100644 --- a/tests/ui/splat/splat-fn-ptr-cast.rs +++ b/tests/ui/splat/splat-fn-ptr-cast.rs @@ -8,9 +8,9 @@ fn main() { // Bug #158603 regression test variants #[rustfmt::skip] - let _x: fn(#[splat] (i32,)) = None.unwrap(); + let _x: fn(#[splat] (f32,)) = None.unwrap(); // FIXME(splat): causes an ICE until #158603 is fixed - //x(1); + //x(1.0); let x: fn((i32,)) = None.unwrap(); x((1,)); diff --git a/tests/ui/splat/splat-fn-ptr-cast.stderr b/tests/ui/splat/splat-fn-ptr-cast.stderr deleted file mode 100644 index 040007f6b9cd8..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-cast.stderr +++ /dev/null @@ -1,10 +0,0 @@ -warning: feature `splat` is declared but not used - --> $DIR/splat-fn-ptr-cast.rs:5:12 - | -LL | #![feature(splat)] - | ^^^^^ - | - = note: `#[warn(unused_features)]` (part of `#[warn(unused)]`) on by default - -warning: 1 warning emitted - diff --git a/tests/ui/splat/splat-fn-ptr-rust-call.rs b/tests/ui/splat/splat-fn-ptr-rust-call.rs new file mode 100644 index 0000000000000..a9fe48a459d8a --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-rust-call.rs @@ -0,0 +1,18 @@ +//! Test using `#[splat]` on tuple arguments of pointers to "rust-call" functions. +//! Currently ICEs at a later stage, but AST validation should catch it earlier. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(unboxed_closures)] + +extern "rust-call" fn f(#[splat] _: ()) {} //~ ERROR `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + +fn main() { + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + let f2: extern "rust-call" fn(#[splat] ()) = f; //~ ERROR `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + // These errors could be confusing, but they're useful if the user meant to use "rust-call" + // instead of #[splat] + f(); //~ ERROR functions with the "rust-call" ABI must take a single non-self tuple argument + f2(); //~ ERROR functions with the "rust-call" ABI must take a single non-self tuple argument +} diff --git a/tests/ui/splat/splat-fn-ptr-rust-call.stderr b/tests/ui/splat/splat-fn-ptr-rust-call.stderr new file mode 100644 index 0000000000000..6eacdfab5faac --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-rust-call.stderr @@ -0,0 +1,30 @@ +error: `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + --> $DIR/splat-fn-ptr-rust-call.rs:8:1 + | +LL | extern "rust-call" fn f(#[splat] _: ()) {} + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^ + | + = help: remove `#[splat]` or change the ABI + +error: `#[splat]` is not allowed in the arguments of functions with the `rust-call` ABI + --> $DIR/splat-fn-ptr-rust-call.rs:13:13 + | +LL | let f2: extern "rust-call" fn(#[splat] ()) = f; + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^ + | + = help: remove `#[splat]` or change the ABI + +error: functions with the "rust-call" ABI must take a single non-self tuple argument + --> $DIR/splat-fn-ptr-rust-call.rs:16:5 + | +LL | f(); + | ^^^ + +error: functions with the "rust-call" ABI must take a single non-self tuple argument + --> $DIR/splat-fn-ptr-rust-call.rs:17:5 + | +LL | f2(); + | ^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/splat/splat-invalid.rs b/tests/ui/splat/splat-invalid.rs index 2c4bf57ef972a..1fe4d131fc584 100644 --- a/tests/ui/splat/splat-invalid.rs +++ b/tests/ui/splat/splat-invalid.rs @@ -4,14 +4,32 @@ #![feature(splat)] #![feature(c_variadic)] -fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} -//~^ ERROR multiple `#[splat]`s are not allowed in the same function +fn multisplat_fn_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function argument list + +fn multisplat_arg_bad( + #[splat] + #[splat] + //~^ ERROR multiple `splat` attributes + (_a, _b): (u32, i8), +) { +} + +fn multisplat_arg_fn_bad( + #[splat] + //~^ ERROR multiple `#[splat]`s are not allowed in the same function argument list + #[splat] + //~^ ERROR multiple `splat` attributes + (_a, _b): (u32, i8), + #[splat] (_c, _d): (u32, i8), +) { +} unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} -//~^ ERROR `...` and `#[splat]` are not allowed in the same function +//~^ ERROR `...` and `#[splat]` are not allowed in the same function argument list unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} -//~^ ERROR `...` and `#[splat]` are not allowed in the same function +//~^ ERROR `...` and `#[splat]` are not allowed in the same function argument list //~| ERROR `...` must be the last argument of a C-variadic function extern "C" { diff --git a/tests/ui/splat/splat-invalid.stderr b/tests/ui/splat/splat-invalid.stderr index 74c54c00cd285..86b1dbbb7e44b 100644 --- a/tests/ui/splat/splat-invalid.stderr +++ b/tests/ui/splat/splat-invalid.stderr @@ -1,35 +1,49 @@ -error: multiple `#[splat]`s are not allowed in the same function - --> $DIR/splat-invalid.rs:7:19 +error: multiple `#[splat]`s are not allowed in the same function argument list + --> $DIR/splat-invalid.rs:7:22 | -LL | fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn multisplat_fn_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^ ^^^^^^^^ | = help: remove `#[splat]` from all but one argument -error: `...` and `#[splat]` are not allowed in the same function - --> $DIR/splat-invalid.rs:10:37 +error: multiple `#[splat]`s are not allowed in the same function argument list + --> $DIR/splat-invalid.rs:19:5 + | +LL | #[splat] + | ^^^^^^^^ +LL | +LL | #[splat] + | ^^^^^^^^ +... +LL | #[splat] (_c, _d): (u32, i8), + | ^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error: `...` and `#[splat]` are not allowed in the same function argument list + --> $DIR/splat-invalid.rs:28:37 | LL | unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ + | ^^^^^^^^ ^^^^^^^^^^^^ | = help: remove `#[splat]` or remove `...` error: `...` must be the last argument of a C-variadic function - --> $DIR/splat-invalid.rs:13:38 + --> $DIR/splat-invalid.rs:31:38 | LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} | ^^^^^^^^^^^^ -error: `...` and `#[splat]` are not allowed in the same function - --> $DIR/splat-invalid.rs:13:38 +error: `...` and `#[splat]` are not allowed in the same function argument list + --> $DIR/splat-invalid.rs:31:38 | LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} - | ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ ^^^^^^^^ | = help: remove `#[splat]` or remove `...` error: incorrect function inside `extern` block - --> $DIR/splat-invalid.rs:18:8 + --> $DIR/splat-invalid.rs:36:8 | LL | extern "C" { | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body @@ -39,16 +53,16 @@ LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html -error: `...` and `#[splat]` are not allowed in the same function - --> $DIR/splat-invalid.rs:18:24 +error: `...` and `#[splat]` are not allowed in the same function argument list + --> $DIR/splat-invalid.rs:36:24 | LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ + | ^^^^^^^^ ^^^ | = help: remove `#[splat]` or remove `...` error: incorrect function inside `extern` block - --> $DIR/splat-invalid.rs:22:8 + --> $DIR/splat-invalid.rs:40:8 | LL | extern "C" { | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body @@ -60,27 +74,51 @@ LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html error: `...` must be the last argument of a C-variadic function - --> $DIR/splat-invalid.rs:22:24 + --> $DIR/splat-invalid.rs:40:24 | LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} | ^^^ -error: `...` and `#[splat]` are not allowed in the same function - --> $DIR/splat-invalid.rs:22:24 +error: `...` and `#[splat]` are not allowed in the same function argument list + --> $DIR/splat-invalid.rs:40:24 | LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} - | ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^ ^^^^^^^^ | = help: remove `#[splat]` or remove `...` +error: multiple `splat` attributes + --> $DIR/splat-invalid.rs:12:5 + | +LL | #[splat] + | ^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/splat-invalid.rs:11:5 + | +LL | #[splat] + | ^^^^^^^^ + +error: multiple `splat` attributes + --> $DIR/splat-invalid.rs:21:5 + | +LL | #[splat] + | ^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/splat-invalid.rs:19:5 + | +LL | #[splat] + | ^^^^^^^^ + error[E0053]: method `has_splat` has an incompatible type for trait - --> $DIR/splat-invalid.rs:42:5 + --> $DIR/splat-invalid.rs:60:5 | LL | fn has_splat(_: ()) {} | ^^^^^^^^^^^^^^^^^^^ expected fn with arg 0 splatted, found fn with no splatted arg | note: type in trait - --> $DIR/splat-invalid.rs:34:5 + --> $DIR/splat-invalid.rs:52:5 | LL | fn has_splat(#[splat] _: ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,19 +126,19 @@ LL | fn has_splat(#[splat] _: ()); found signature `fn(())` error[E0053]: method `no_splat` has an incompatible type for trait - --> $DIR/splat-invalid.rs:44:5 + --> $DIR/splat-invalid.rs:62:5 | LL | fn no_splat(#[splat] _: (u32, f64)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn with no splatted arg, found fn with arg 0 splatted | note: type in trait - --> $DIR/splat-invalid.rs:36:5 + --> $DIR/splat-invalid.rs:54:5 | LL | fn no_splat(_: (u32, f64)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: expected signature `fn((_, _))` found signature `fn(#[splat] (_, _))` -error: aborting due to 11 previous errors +error: aborting due to 14 previous errors For more information about this error, try `rustc --explain E0053`. diff --git a/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs b/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs index 3f29b9d292d22..473aff7891636 100644 --- a/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs +++ b/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs @@ -7,7 +7,7 @@ unsafe fn unsafe_wrong_type(#[splat] _x: u32) {} //~^ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 unsafe fn unsafe_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} -//~^ ERROR multiple `#[splat]`s are not allowed in the same function +//~^ ERROR multiple `#[splat]`s are not allowed in the same function argument list fn main() { unsafe { diff --git a/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr b/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr index 67935e170671a..7ad15f0f960e2 100644 --- a/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr +++ b/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr @@ -1,8 +1,8 @@ -error: multiple `#[splat]`s are not allowed in the same function +error: multiple `#[splat]`s are not allowed in the same function argument list --> $DIR/splat-unsafe-fn-tuple-fail.rs:9:30 | LL | unsafe fn unsafe_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ ^^^^^^^^ | = help: remove `#[splat]` from all but one argument From 042e779afae728bfd491bcfb690908dc762d82b5 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 7 Jul 2026 00:15:26 -0700 Subject: [PATCH 33/78] Add diagram to comment Co-authored-by: lolbinarycat --- compiler/rustc_resolve/src/rustdoc.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index f1e94b415787b..301ea7c71da06 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -719,6 +719,12 @@ pub fn source_span_for_markdown_range_inner( // but we found a non-empty non-markdown line. // This could be an attribute, and we don't want a diagnostic // suggesting to delete that attribute, so we return None to be safe. + // 1| /** doc */ + // 2 | #[inline] + // ^^^^^^^^^ + // | this + // 3| /** doc2 */ + // 4| fn foo() {} return None; } else { end_bytes += source_line.len() + 1; From 8f1fd2c6e4bf7e44dfca254e162120d260144983 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 7 Jul 2026 00:16:01 -0700 Subject: [PATCH 34/78] Fix wrong diagram in comment Co-authored-by: lolbinarycat --- compiler/rustc_resolve/src/rustdoc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 301ea7c71da06..ddf5600f76659 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -690,7 +690,7 @@ pub fn source_span_for_markdown_range_inner( // ^^^^^^^^^ // | this // - // 2| /** doc2 */ fn foo() {} + // 2| /** doc2 */ // 3| fn foo() {} return None; } From fd07857db3c77538b9214c4125aaa91ee48481d4 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 7 Jul 2026 00:17:23 -0700 Subject: [PATCH 35/78] Use better name for lint struct Co-authored-by: lolbinarycat --- .../passes/lint/redundant_explicit_links.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index ad45b6e1d1c5b..cd7b7caac69ad 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -154,13 +154,13 @@ fn check_redundant_explicit_link<'md>( } } -struct RedundantExplicitLinksCannotSuggest { +struct RedundantExplicitLinksWithoutSuggestion { attr_span: Span, display_link: String, dest_link: String, } -impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksCannotSuggest { +impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksWithoutSuggestion { fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { let Self { attr_span, display_link, dest_link } = self; @@ -186,7 +186,7 @@ fn check_inline_or_reference_unknown_redundancy( dest: String, link_data: LinkData, (open, close): (u8, u8), -) -> Result<(), RedundantExplicitLinksCannotSuggest> { +) -> Result<(), RedundantExplicitLinksWithoutSuggestion> { struct RedundantExplicitLinks { explicit_span: Span, display_span: Span, @@ -256,7 +256,7 @@ fn check_inline_or_reference_unknown_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -274,7 +274,7 @@ fn check_inline_or_reference_unknown_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -308,7 +308,7 @@ fn check_reference_redundancy( link_range: Range, dest: &CowStr<'_>, link_data: LinkData, -) -> Result<(), RedundantExplicitLinksCannotSuggest> { +) -> Result<(), RedundantExplicitLinksWithoutSuggestion> { struct RedundantExplicitLinkTarget { explicit_span: Span, display_span: Span, @@ -378,7 +378,7 @@ fn check_reference_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -396,7 +396,7 @@ fn check_reference_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -412,7 +412,7 @@ fn check_reference_redundancy( Some((def_span, _)) => def_span, // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, From bbba4966b2c0b8b95d54f319cfbb68b8c906da86 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Tue, 7 Jul 2026 11:58:27 +0000 Subject: [PATCH 36/78] Propagate errors from list_file_metadata into list_metadata Update unstable book with ls valid values --- compiler/rustc_driver_impl/src/lib.rs | 23 ++++++++++--------- compiler/rustc_metadata/src/locator.rs | 7 ++++-- .../unstable-book/src/compiler-flags/ls.md | 8 +++++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index cf1271878a316..98663d3db1335 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -597,7 +597,18 @@ fn process_rlink(sess: &Session, compiler: &interface::Compiler) { fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { match sess.io.input { Input::File(ref path) => { - if path.extension().is_some_and(|extension| extension == "rs") { + let mut v = Vec::new(); + if locator::list_file_metadata( + &sess.target, + path, + metadata_loader, + &mut v, + &sess.opts.unstable_opts.ls, + sess.cfg_version, + ) + .is_err() + && path.extension().is_some_and(|extension| extension == "rs") + { let mut err = sess .dcx() .struct_fatal("`-Zls` takes a `.rmeta` file as input, not a source file"); @@ -607,16 +618,6 @@ fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { } err.emit(); } - let mut v = Vec::new(); - locator::list_file_metadata( - &sess.target, - path, - metadata_loader, - &mut v, - &sess.opts.unstable_opts.ls, - sess.cfg_version, - ) - .unwrap(); safe_println!("{}", String::from_utf8(v).unwrap()); } Input::Str { .. } => { diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 874d4812502e6..f0502952bd2fa 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -213,7 +213,7 @@ //! metadata::locator or metadata::creader for all the juicy details! use std::borrow::Cow; -use std::io::{self, Result as IoResult, Write}; +use std::io::{self, Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write}; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::{cmp, fmt}; @@ -963,7 +963,10 @@ pub fn list_file_metadata( let flavor = get_flavor_from_path(path); match get_metadata_section(target, flavor, path, metadata_loader, cfg_version, None) { Ok(metadata) => metadata.list_crate_metadata(out, ls_kinds), - Err(msg) => write!(out, "{msg}\n"), + Err(msg) => { + let _ = write!(out, "{msg}"); + Err(IoError::new(IoErrorKind::Other, msg.to_string())) + } } } diff --git a/src/doc/unstable-book/src/compiler-flags/ls.md b/src/doc/unstable-book/src/compiler-flags/ls.md index c0ad80d6e86e6..abb85acd1e58f 100644 --- a/src/doc/unstable-book/src/compiler-flags/ls.md +++ b/src/doc/unstable-book/src/compiler-flags/ls.md @@ -8,6 +8,14 @@ This allows for debugging the metadata emitted by an earlier compilation. Note that, while `rustc` usually works with `.rs` files, this option is meant purely for analyzing `.rmeta` files, and does not produce any compilation artifact. +Allowed values are: + +- `root`: Crate info. +- `lang_items`: Language items used and missing, if any. +- `features`: Library features used. +- `items`: All items (such as modules, functions...) in the crate, including attributes like their visibility +- `all`: All of the above + ## Example ```sh From ab783ad752014e8f7b99df1cab16e5c472af9489 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 24 Jun 2026 00:56:23 +0200 Subject: [PATCH 37/78] accept extern calls to compiler-builtin functions in `is_call_from_compiler_builtins_to_upstream_monomorphization`, suggested by Saethlin --- compiler/rustc_codegen_ssa/src/base.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 4e979df471318..5b235bcba7b43 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -883,6 +883,10 @@ pub fn codegen_crate< /// unlinkable calls. /// /// Note that calls to LLVM intrinsics are uniquely okay because they won't make it to the linker. +/// Note also that calls to foreign items that are actually exported by the local crate are also +/// okay. This situation arises because compiler-builtins calls functions in core that are +/// `#[inline]` wrappers for `extern "C"` declarations in core, which resolve to a symbol exported +/// by compiler-builtins. pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>( tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, @@ -895,11 +899,19 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>( } } + fn is_extern_call_to_local_crate<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { + tcx.is_foreign_item(instance.def_id()) + && tcx.exported_non_generic_symbols(LOCAL_CRATE).iter().any(|(sym, _info)| { + sym.symbol_name_for_local_instance(tcx) == tcx.symbol_name(instance) + }) + } + let def_id = instance.def_id(); !def_id.is_local() && tcx.is_compiler_builtins(LOCAL_CRATE) && !is_llvm_intrinsic(tcx, def_id) && !tcx.should_codegen_locally(instance) + && !is_extern_call_to_local_crate(tcx, instance) } impl CrateInfo { From 47acba7c146471017f651e865def06af1b87968e Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 4 Jul 2026 17:31:26 +0200 Subject: [PATCH 38/78] remove `-Zmiri-force-intrinsic-fallback` test run We'll do this using `-Zforce-intrinsic-fallback` in the main repo going forward --- src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh b/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh index c2a5b308b3284..0ccd364be21bb 100755 --- a/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh +++ b/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh @@ -15,14 +15,6 @@ if [ -z "${PR_CI_JOB:-}" ]; then else python3 "$X_PY" test --stage 2 src/tools/miri src/tools/miri/cargo-miri fi -# We re-run the test suite for a chance to find bugs in the intrinsic fallback bodies and in MIR -# optimizations. This can miss UB, so we only run the "pass" tests. We need to enable debug -# assertions as `-O` disables them but some tests rely on them. We also set a cfg flag so tests can -# adjust their expectations if needed. This can change the output of the tests so we ignore that, -# we only ensure that all assertions still pass. -MIRIFLAGS="-Zmiri-force-intrinsic-fallback --cfg force_intrinsic_fallback -O -Zmir-opt-level=4 -Cdebug-assertions=yes" \ - MIRI_SKIP_UI_CHECKS=1 \ - python3 "$X_PY" test --stage 2 src/tools/miri -- tests/pass tests/panic # We natively run this script on x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc. # Also cover some other targets via cross-testing, in particular all tier 1 targets. case $HOST_TARGET in From 63bec90ff294fcbeca39f3fcb1b18a3b17c85d6f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 10 Jan 2026 17:32:06 -0600 Subject: [PATCH 39/78] intrinsics: Add a fallback for non-const libm float functions A number of float operations from libm have intrinsics for optimization, but it is also okay to just call the libm functions directly. Add a fallback for these cases, including converting to/from another float size where needed, so the backends don't need to override these. We do not add a fallback body for intrinsics that have a softfloat implementation (e.g. sqrt, fma), because it would make them harder to constify later. --- compiler/rustc_codegen_ssa/src/base.rs | 4 +- library/core/src/intrinsics/mod.rs | 161 ++++++++++++++++++++----- library/core/src/num/imp/libm.rs | 148 ++++++++++++++++++++++- 3 files changed, 278 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 5b235bcba7b43..b9684eb52f3f4 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -877,9 +877,9 @@ pub fn codegen_crate< /// Returns whether a call from the current crate to the [`Instance`] would produce a call /// from `compiler_builtins` to a symbol the linker must resolve. /// -/// Such calls from `compiler_bultins` are effectively impossible for the linker to handle. Some +/// Such calls from `compiler_builtins` are effectively impossible for the linker to handle. Some /// linkers will optimize such that dead calls to unresolved symbols are not an error, but this is -/// not guaranteed. So we used this function in codegen backends to ensure we do not generate any +/// not guaranteed. So we use this function in codegen backends to ensure we do not generate any /// unlinkable calls. /// /// Note that calls to LLVM intrinsics are uniquely okay because they won't make it to the linker. diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index bc96c768c0c94..84e8aa9569fec 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -55,6 +55,7 @@ use crate::ffi::{VaArgSafe, VaList}; use crate::marker::{ConstParamTy, DiscriminantKind, PointeeSized, Tuple}; +use crate::num::imp::libm; use crate::{mem, ptr}; mod bounds; @@ -1083,233 +1084,329 @@ pub fn powif128(a: f128, x: i32) -> f128; /// /// The stabilized version of this intrinsic is /// [`f16::sin`](../../std/primitive.f16.html#method.sin) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn sinf16(x: f16) -> f16; +pub fn sinf16(x: f16) -> f16 { + libm::likely_available::sinf(x as f32) as f16 +} /// Returns the sine of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::sin`](../../std/primitive.f32.html#method.sin) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn sinf32(x: f32) -> f32; +pub fn sinf32(x: f32) -> f32 { + libm::likely_available::sinf(x) +} /// Returns the sine of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::sin`](../../std/primitive.f64.html#method.sin) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn sinf64(x: f64) -> f64; +pub fn sinf64(x: f64) -> f64 { + libm::likely_available::sin(x) +} /// Returns the sine of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::sin`](../../std/primitive.f128.html#method.sin) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn sinf128(x: f128) -> f128; +pub fn sinf128(x: f128) -> f128 { + libm::maybe_available::sinf128(x) +} /// Returns the cosine of an `f16`. /// /// The stabilized version of this intrinsic is /// [`f16::cos`](../../std/primitive.f16.html#method.cos) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn cosf16(x: f16) -> f16; +pub fn cosf16(x: f16) -> f16 { + libm::likely_available::cosf(x as f32) as f16 +} /// Returns the cosine of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::cos`](../../std/primitive.f32.html#method.cos) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn cosf32(x: f32) -> f32; +pub fn cosf32(x: f32) -> f32 { + libm::likely_available::cosf(x) +} /// Returns the cosine of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::cos`](../../std/primitive.f64.html#method.cos) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn cosf64(x: f64) -> f64; +pub fn cosf64(x: f64) -> f64 { + libm::likely_available::cos(x) +} /// Returns the cosine of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::cos`](../../std/primitive.f128.html#method.cos) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn cosf128(x: f128) -> f128; +pub fn cosf128(x: f128) -> f128 { + libm::maybe_available::cosf128(x) +} /// Raises an `f16` to an `f16` power. /// /// The stabilized version of this intrinsic is /// [`f16::powf`](../../std/primitive.f16.html#method.powf) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn powf16(a: f16, x: f16) -> f16; +pub fn powf16(a: f16, x: f16) -> f16 { + libm::likely_available::powf(a as f32, x as f32) as f16 +} /// Raises an `f32` to an `f32` power. /// /// The stabilized version of this intrinsic is /// [`f32::powf`](../../std/primitive.f32.html#method.powf) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn powf32(a: f32, x: f32) -> f32; +pub fn powf32(a: f32, x: f32) -> f32 { + libm::likely_available::powf(a, x) +} /// Raises an `f64` to an `f64` power. /// /// The stabilized version of this intrinsic is /// [`f64::powf`](../../std/primitive.f64.html#method.powf) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn powf64(a: f64, x: f64) -> f64; +pub fn powf64(a: f64, x: f64) -> f64 { + libm::likely_available::pow(a, x) +} /// Raises an `f128` to an `f128` power. /// /// The stabilized version of this intrinsic is /// [`f128::powf`](../../std/primitive.f128.html#method.powf) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn powf128(a: f128, x: f128) -> f128; +pub fn powf128(a: f128, x: f128) -> f128 { + libm::maybe_available::powf128(a, x) +} /// Returns the exponential of an `f16`. /// /// The stabilized version of this intrinsic is /// [`f16::exp`](../../std/primitive.f16.html#method.exp) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn expf16(x: f16) -> f16; +pub fn expf16(x: f16) -> f16 { + libm::likely_available::expf(x as f32) as f16 +} /// Returns the exponential of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::exp`](../../std/primitive.f32.html#method.exp) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn expf32(x: f32) -> f32; +pub fn expf32(x: f32) -> f32 { + libm::likely_available::expf(x) +} /// Returns the exponential of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::exp`](../../std/primitive.f64.html#method.exp) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn expf64(x: f64) -> f64; +pub fn expf64(x: f64) -> f64 { + libm::likely_available::exp(x) +} /// Returns the exponential of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::exp`](../../std/primitive.f128.html#method.exp) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn expf128(x: f128) -> f128; +pub fn expf128(x: f128) -> f128 { + libm::maybe_available::expf128(x) +} /// Returns 2 raised to the power of an `f16`. /// /// The stabilized version of this intrinsic is /// [`f16::exp2`](../../std/primitive.f16.html#method.exp2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn exp2f16(x: f16) -> f16; +pub fn exp2f16(x: f16) -> f16 { + libm::likely_available::exp2f(x as f32) as f16 +} /// Returns 2 raised to the power of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::exp2`](../../std/primitive.f32.html#method.exp2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn exp2f32(x: f32) -> f32; +pub fn exp2f32(x: f32) -> f32 { + libm::likely_available::exp2f(x) +} /// Returns 2 raised to the power of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::exp2`](../../std/primitive.f64.html#method.exp2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn exp2f64(x: f64) -> f64; +pub fn exp2f64(x: f64) -> f64 { + libm::likely_available::exp2(x) +} /// Returns 2 raised to the power of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::exp2`](../../std/primitive.f128.html#method.exp2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn exp2f128(x: f128) -> f128; +pub fn exp2f128(x: f128) -> f128 { + libm::maybe_available::exp2f128(x) +} /// Returns the natural logarithm of an `f16`. /// /// The stabilized version of this intrinsic is /// [`f16::ln`](../../std/primitive.f16.html#method.ln) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn logf16(x: f16) -> f16; +pub fn logf16(x: f16) -> f16 { + libm::likely_available::logf(x as f32) as f16 +} /// Returns the natural logarithm of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::ln`](../../std/primitive.f32.html#method.ln) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn logf32(x: f32) -> f32; +pub fn logf32(x: f32) -> f32 { + libm::likely_available::logf(x) +} /// Returns the natural logarithm of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::ln`](../../std/primitive.f64.html#method.ln) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn logf64(x: f64) -> f64; +pub fn logf64(x: f64) -> f64 { + libm::likely_available::log(x) +} /// Returns the natural logarithm of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::ln`](../../std/primitive.f128.html#method.ln) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn logf128(x: f128) -> f128; +pub fn logf128(x: f128) -> f128 { + libm::maybe_available::logf128(x) +} /// Returns the base 10 logarithm of an `f16`. /// /// The stabilized version of this intrinsic is /// [`f16::log10`](../../std/primitive.f16.html#method.log10) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log10f16(x: f16) -> f16; +pub fn log10f16(x: f16) -> f16 { + libm::likely_available::log10f(x as f32) as f16 +} /// Returns the base 10 logarithm of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::log10`](../../std/primitive.f32.html#method.log10) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log10f32(x: f32) -> f32; +pub fn log10f32(x: f32) -> f32 { + libm::likely_available::log10f(x) +} /// Returns the base 10 logarithm of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::log10`](../../std/primitive.f64.html#method.log10) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log10f64(x: f64) -> f64; +pub fn log10f64(x: f64) -> f64 { + libm::likely_available::log10(x) +} /// Returns the base 10 logarithm of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::log10`](../../std/primitive.f128.html#method.log10) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log10f128(x: f128) -> f128; +pub fn log10f128(x: f128) -> f128 { + libm::maybe_available::log10f128(x) +} /// Returns the base 2 logarithm of an `f16`. /// /// The stabilized version of this intrinsic is /// [`f16::log2`](../../std/primitive.f16.html#method.log2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log2f16(x: f16) -> f16; +pub fn log2f16(x: f16) -> f16 { + libm::likely_available::log2f(x as f32) as f16 +} /// Returns the base 2 logarithm of an `f32`. /// /// The stabilized version of this intrinsic is /// [`f32::log2`](../../std/primitive.f32.html#method.log2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log2f32(x: f32) -> f32; +pub fn log2f32(x: f32) -> f32 { + libm::likely_available::log2f(x) +} /// Returns the base 2 logarithm of an `f64`. /// /// The stabilized version of this intrinsic is /// [`f64::log2`](../../std/primitive.f64.html#method.log2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log2f64(x: f64) -> f64; +pub fn log2f64(x: f64) -> f64 { + libm::likely_available::log2(x) +} /// Returns the base 2 logarithm of an `f128`. /// /// The stabilized version of this intrinsic is /// [`f128::log2`](../../std/primitive.f128.html#method.log2) +#[inline] #[rustc_intrinsic] #[rustc_nounwind] -pub fn log2f128(x: f128) -> f128; +pub fn log2f128(x: f128) -> f128 { + libm::maybe_available::log2f128(x) +} /// Returns `a * b + c` for `f16` values. /// diff --git a/library/core/src/num/imp/libm.rs b/library/core/src/num/imp/libm.rs index aeabb08723095..c67adc337c1cb 100644 --- a/library/core/src/num/imp/libm.rs +++ b/library/core/src/num/imp/libm.rs @@ -1,11 +1,157 @@ //! Bindings to math functions provided by the system `libm` or by the `libm` crate, exposed //! via `compiler-builtins`. +//! +//! The functions in the root of this module are "guaranteed" to be available; see the +//! `full_availability` module in compiler-builtins for details. // SAFETY: These symbols have standard interfaces in C and are defined by `libm`, or are // provided by `compiler-builtins` on unsupported platforms. +#[allow(dead_code)] // This list reflects what is available rather than what is consumed. unsafe extern "C" { - pub(crate) safe fn cbrt(n: f64) -> f64; + pub(crate) safe fn cbrt(x: f64) -> f64; pub(crate) safe fn cbrtf(n: f32) -> f32; + pub(crate) safe fn ceil(x: f64) -> f64; + pub(crate) safe fn ceilf(x: f32) -> f32; + pub(crate) safe fn ceilf128(x: f128) -> f128; + pub(crate) safe fn ceilf16(x: f16) -> f16; + pub(crate) safe fn copysign(x: f64, y: f64) -> f64; + pub(crate) safe fn copysignf(x: f32, y: f32) -> f32; + pub(crate) safe fn copysignf128(x: f128, y: f128) -> f128; + pub(crate) safe fn copysignf16(x: f16, y: f16) -> f16; + pub(crate) safe fn fabs(x: f64) -> f64; + pub(crate) safe fn fabsf(x: f32) -> f32; + pub(crate) safe fn fabsf128(x: f128) -> f128; + pub(crate) safe fn fabsf16(x: f16) -> f16; pub(crate) safe fn fdim(a: f64, b: f64) -> f64; pub(crate) safe fn fdimf(a: f32, b: f32) -> f32; + pub(crate) safe fn fdimf128(x: f128, y: f128) -> f128; + pub(crate) safe fn fdimf16(x: f16, y: f16) -> f16; + pub(crate) safe fn floor(x: f64) -> f64; + pub(crate) safe fn floorf(x: f32) -> f32; + pub(crate) safe fn floorf128(x: f128) -> f128; + pub(crate) safe fn floorf16(x: f16) -> f16; + pub(crate) safe fn fma(x: f64, y: f64, z: f64) -> f64; + pub(crate) safe fn fmaf(x: f32, y: f32, z: f32) -> f32; + pub(crate) safe fn fmaf128(x: f128, y: f128, z: f128) -> f128; + pub(crate) safe fn fmax(x: f64, y: f64) -> f64; + pub(crate) safe fn fmaxf(x: f32, y: f32) -> f32; + pub(crate) safe fn fmaxf128(x: f128, y: f128) -> f128; + pub(crate) safe fn fmaxf16(x: f16, y: f16) -> f16; + pub(crate) safe fn fmaximum(x: f64, y: f64) -> f64; + pub(crate) safe fn fmaximumf(x: f32, y: f32) -> f32; + pub(crate) safe fn fmaximumf128(x: f128, y: f128) -> f128; + pub(crate) safe fn fmaximumf16(x: f16, y: f16) -> f16; + pub(crate) safe fn fmin(x: f64, y: f64) -> f64; + pub(crate) safe fn fminf(x: f32, y: f32) -> f32; + pub(crate) safe fn fminf128(x: f128, y: f128) -> f128; + pub(crate) safe fn fminf16(x: f16, y: f16) -> f16; + pub(crate) safe fn fminimum(x: f64, y: f64) -> f64; + pub(crate) safe fn fminimumf(x: f32, y: f32) -> f32; + pub(crate) safe fn fminimumf128(x: f128, y: f128) -> f128; + pub(crate) safe fn fminimumf16(x: f16, y: f16) -> f16; + pub(crate) safe fn fmod(x: f64, y: f64) -> f64; + pub(crate) safe fn fmodf(x: f32, y: f32) -> f32; + pub(crate) safe fn fmodf128(x: f128, y: f128) -> f128; + pub(crate) safe fn fmodf16(x: f16, y: f16) -> f16; + pub(crate) safe fn rint(x: f64) -> f64; + pub(crate) safe fn rintf(x: f32) -> f32; + pub(crate) safe fn rintf128(x: f128) -> f128; + pub(crate) safe fn rintf16(x: f16) -> f16; + pub(crate) safe fn round(x: f64) -> f64; + pub(crate) safe fn roundeven(x: f64) -> f64; + pub(crate) safe fn roundevenf(x: f32) -> f32; + pub(crate) safe fn roundevenf128(x: f128) -> f128; + pub(crate) safe fn roundevenf16(x: f16) -> f16; + pub(crate) safe fn roundf(x: f32) -> f32; + pub(crate) safe fn roundf128(x: f128) -> f128; + pub(crate) safe fn roundf16(x: f16) -> f16; + pub(crate) safe fn sqrt(x: f64) -> f64; + pub(crate) safe fn sqrtf(x: f32) -> f32; + pub(crate) safe fn sqrtf128(x: f128) -> f128; + pub(crate) safe fn sqrtf16(x: f16) -> f16; + pub(crate) safe fn trunc(x: f64) -> f64; + pub(crate) safe fn truncf(x: f32) -> f32; + pub(crate) safe fn truncf128(x: f128) -> f128; + pub(crate) safe fn truncf16(x: f16) -> f16; +} + +/// These symbols will be available when `std` is available, and on many no-std platforms. However, +/// since this isn't a guarantee, we cannot rely on them for stable implementations. +pub(crate) mod likely_available { + #[allow(dead_code)] + unsafe extern "C" { + pub(crate) safe fn acos(x: f64) -> f64; + pub(crate) safe fn acosf(n: f32) -> f32; + pub(crate) safe fn asin(x: f64) -> f64; + pub(crate) safe fn asinf(n: f32) -> f32; + pub(crate) safe fn atan(x: f64) -> f64; + pub(crate) safe fn atan2(x: f64, y: f64) -> f64; + pub(crate) safe fn atan2f(a: f32, b: f32) -> f32; + pub(crate) safe fn atanf(n: f32) -> f32; + pub(crate) safe fn cos(x: f64) -> f64; + pub(crate) safe fn cosf(x: f32) -> f32; + pub(crate) safe fn cosh(x: f64) -> f64; + pub(crate) safe fn coshf(n: f32) -> f32; + pub(crate) safe fn erf(x: f64) -> f64; + pub(crate) safe fn erfc(x: f64) -> f64; + pub(crate) safe fn erfcf(x: f32) -> f32; + pub(crate) safe fn erff(x: f32) -> f32; + pub(crate) safe fn exp(x: f64) -> f64; + pub(crate) safe fn exp2(x: f64) -> f64; + pub(crate) safe fn exp2f(x: f32) -> f32; + pub(crate) safe fn expf(x: f32) -> f32; + pub(crate) safe fn expm1(x: f64) -> f64; + pub(crate) safe fn expm1f(n: f32) -> f32; + pub(crate) safe fn hypot(x: f64, y: f64) -> f64; + pub(crate) safe fn hypotf(x: f32, y: f32) -> f32; + pub(crate) safe fn ldexp(f: f64, n: i32) -> f64; + pub(crate) safe fn ldexpf(f: f32, n: i32) -> f32; + pub(crate) safe fn log(x: f64) -> f64; + pub(crate) safe fn log10(x: f64) -> f64; + pub(crate) safe fn log10f(x: f32) -> f32; + pub(crate) safe fn log1p(x: f64) -> f64; + pub(crate) safe fn log1pf(n: f32) -> f32; + pub(crate) safe fn log2(x: f64) -> f64; + pub(crate) safe fn log2f(x: f32) -> f32; + pub(crate) safe fn logf(x: f32) -> f32; + pub(crate) safe fn pow(x: f64, y: f64) -> f64; + pub(crate) safe fn powf(x: f32, y: f32) -> f32; + pub(crate) safe fn sin(x: f64) -> f64; + pub(crate) safe fn sinf(x: f32) -> f32; + pub(crate) safe fn sinh(x: f64) -> f64; + pub(crate) safe fn sinhf(n: f32) -> f32; + pub(crate) safe fn tan(x: f64) -> f64; + pub(crate) safe fn tanf(n: f32) -> f32; + pub(crate) safe fn tanh(x: f64) -> f64; + pub(crate) safe fn tanhf(n: f32) -> f32; + pub(crate) safe fn tgamma(x: f64) -> f64; + pub(crate) safe fn tgammaf(x: f32) -> f32; + } +} + +/// These symbols exist on some platforms but do not have a compiler-builtins fallback. +pub(crate) mod maybe_available { + #[allow(dead_code)] + unsafe extern "C" { + pub(crate) safe fn acosf128(x: f128) -> f128; + pub(crate) safe fn asinf128(x: f128) -> f128; + pub(crate) safe fn atanf128(x: f128) -> f128; + pub(crate) safe fn cbrtf128(x: f128) -> f128; + pub(crate) safe fn cosf128(x: f128) -> f128; + pub(crate) safe fn erff128(x: f128) -> f128; + pub(crate) safe fn expf128(x: f128) -> f128; + pub(crate) safe fn exp2f128(x: f128) -> f128; + pub(crate) safe fn expm1f128(x: f128) -> f128; + pub(crate) safe fn hypotf128(x: f128, y: f128) -> f128; + pub(crate) safe fn ldexpf128(f: f128, n: i32) -> f128; + pub(crate) safe fn log10f128(x: f128) -> f128; + pub(crate) safe fn log1pf128(x: f128) -> f128; + pub(crate) safe fn log2f128(x: f128) -> f128; + pub(crate) safe fn logf128(x: f128) -> f128; + pub(crate) safe fn powf128(x: f128, y: f128) -> f128; + pub(crate) safe fn sinf128(x: f128) -> f128; + pub(crate) safe fn tanf128(x: f128) -> f128; + pub(crate) safe fn tanhf128(x: f128) -> f128; + pub(crate) safe fn tgammaf128(x: f128) -> f128; + } } From d20d049725e0af1c2d2f9ead19d7fc168014b74c Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 8 Jul 2026 10:15:22 +1000 Subject: [PATCH 40/78] Reword splat arg index limit error for clarity --- compiler/rustc_ast/src/ast.rs | 3 +++ compiler/rustc_ast_passes/src/ast_validation.rs | 2 +- compiler/rustc_ast_passes/src/diagnostics.rs | 4 ++-- tests/ui/splat/splat-255-limit-fail.rs | 8 ++++---- tests/ui/splat/splat-255-limit-fail.stderr | 8 ++++---- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 3e1fff594040e..728b14246458f 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3064,6 +3064,9 @@ impl FnDecl { /// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `FnDeclFlags::NO_SPLATTED_ARG_INDEX`. pub const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX; + /// The maximum valid splatted argument index. + pub const MAX_VALID_SPLATTED_ARG_INDEX: u8 = Self::NO_SPLATTED_ARG_INDEX - 1; + /// Returns a splatted argument index, if any are present. pub fn splatted(&self) -> Option { self.inputs.iter().enumerate().find_map(|(index, arg)| { diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 6a689422f2956..281f417500c55 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -471,7 +471,7 @@ impl<'a> AstValidator<'a> { splatted_arg_spans.split_off(&u16::from(FnDecl::NO_SPLATTED_ARG_INDEX)); if !out_of_range_spans.is_empty() { self.dcx().emit_err(diagnostics::InvalidSplattedArgs { - min_invalid_splatted_arg_index: u16::from(FnDecl::NO_SPLATTED_ARG_INDEX), + max_valid_splatted_arg_index: u16::from(FnDecl::MAX_VALID_SPLATTED_ARG_INDEX), first_invalid_splatted_arg_index: *out_of_range_spans.keys().next().unwrap(), spans: out_of_range_spans.values().flatten().copied().collect(), }); diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 90b82f102fd2d..0b610431cadd8 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -125,11 +125,11 @@ pub(crate) struct FnParamCVarArgsNotLast { #[derive(Diagnostic)] #[diag( - "`#[splat]` is not supported on argument index {$min_invalid_splatted_arg_index} or greater, but is on index {$first_invalid_splatted_arg_index}" + "`#[splat]` is only supported on argument index {$max_valid_splatted_arg_index} or less, this `#[splat]` is on index {$first_invalid_splatted_arg_index}" )] #[help("remove `#[splat]`, or use it on an argument closer to the start of the argument list")] pub(crate) struct InvalidSplattedArgs { - pub min_invalid_splatted_arg_index: u16, + pub max_valid_splatted_arg_index: u16, pub first_invalid_splatted_arg_index: u16, diff --git a/tests/ui/splat/splat-255-limit-fail.rs b/tests/ui/splat/splat-255-limit-fail.rs index 77d9d84ec30da..f19ce37bc4566 100644 --- a/tests/ui/splat/splat-255-limit-fail.rs +++ b/tests/ui/splat/splat-255-limit-fail.rs @@ -47,7 +47,7 @@ fn s_255_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 255 ) {} #[rustfmt::skip] @@ -68,7 +68,7 @@ fn s_256_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 or greater, but is on index 256 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 256 ) {} #[rustfmt::skip] @@ -88,7 +88,7 @@ fn s_255_non_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 or greater, but is on index 255 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 255 _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, ) {} @@ -110,7 +110,7 @@ fn s_256_non_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, - #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 or greater, but is on index 256 + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 256 _: A, ) {} diff --git a/tests/ui/splat/splat-255-limit-fail.stderr b/tests/ui/splat/splat-255-limit-fail.stderr index 20801601930f1..a3a0b9cf3d350 100644 --- a/tests/ui/splat/splat-255-limit-fail.stderr +++ b/tests/ui/splat/splat-255-limit-fail.stderr @@ -1,4 +1,4 @@ -error: `#[splat]` is not supported on argument index 255 or greater, but is on index 255 +error: `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 255 --> $DIR/splat-255-limit-fail.rs:50:5 | LL | #[splat] (_a, _b): (u32, i8), @@ -6,7 +6,7 @@ LL | #[splat] (_a, _b): (u32, i8), | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: `#[splat]` is not supported on argument index 255 or greater, but is on index 256 +error: `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 256 --> $DIR/splat-255-limit-fail.rs:71:5 | LL | #[splat] (_a, _b): (u32, i8), @@ -14,7 +14,7 @@ LL | #[splat] (_a, _b): (u32, i8), | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: `#[splat]` is not supported on argument index 255 or greater, but is on index 255 +error: `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 255 --> $DIR/splat-255-limit-fail.rs:91:5 | LL | #[splat] (_a, _b): (u32, i8), @@ -22,7 +22,7 @@ LL | #[splat] (_a, _b): (u32, i8), | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: `#[splat]` is not supported on argument index 255 or greater, but is on index 256 +error: `#[splat]` is only supported on argument index 254 or less, this `#[splat]` is on index 256 --> $DIR/splat-255-limit-fail.rs:113:5 | LL | #[splat] (_a, _b): (u32, i8), From 67986afdaf544482bc89fc1be5de309d4dbbcf89 Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 8 Jul 2026 07:46:21 +0200 Subject: [PATCH 41/78] allow mGCA const arguments to fall back to anon consts --- compiler/rustc_ast/src/ast.rs | 16 +- compiler/rustc_ast/src/util/classify.rs | 7 +- compiler/rustc_ast/src/visit.rs | 3 +- compiler/rustc_ast_lowering/src/expr.rs | 18 +- compiler/rustc_ast_lowering/src/lib.rs | 293 ++++++++++++------ compiler/rustc_ast_pretty/src/pprust/state.rs | 6 + .../rustc_ast_pretty/src/pprust/state/expr.rs | 6 + .../src/assert/context.rs | 1 + compiler/rustc_builtin_macros/src/autodiff.rs | 10 +- .../src/direct_const_arg.rs | 39 +++ compiler/rustc_builtin_macros/src/lib.rs | 2 + .../rustc_builtin_macros/src/pattern_type.rs | 18 +- compiler/rustc_expand/src/build.rs | 5 +- .../src/collect/generics_of.rs | 11 + compiler/rustc_metadata/src/rmeta/encoder.rs | 24 +- compiler/rustc_parse/src/parser/asm.rs | 4 +- .../rustc_parse/src/parser/diagnostics.rs | 20 +- compiler/rustc_parse/src/parser/expr.rs | 18 +- compiler/rustc_parse/src/parser/item.rs | 17 +- compiler/rustc_parse/src/parser/mod.rs | 5 +- compiler/rustc_parse/src/parser/path.rs | 23 +- compiler/rustc_parse/src/parser/ty.rs | 12 +- compiler/rustc_passes/src/input_stats.rs | 5 +- compiler/rustc_resolve/src/def_collector.rs | 23 +- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/marker.rs | 14 + .../clippy_utils/src/check_proc_macro.rs | 1 + src/tools/clippy/clippy_utils/src/sugg.rs | 1 + src/tools/rustfmt/src/expr.rs | 3 +- src/tools/rustfmt/src/types.rs | 5 +- src/tools/rustfmt/src/utils.rs | 5 +- .../rustfmt/tests/source/direct_const_arg.rs | 14 + .../rustfmt/tests/target/direct_const_arg.rs | 14 + tests/pretty/direct-const-arg.pp | 15 + tests/pretty/direct-const-arg.rs | 10 + .../doesnt_unify_evaluatable.stderr | 4 +- .../mgca/adt_expr_arg_simple.rs | 3 +- .../mgca/adt_expr_arg_simple.stderr | 8 +- .../mgca/array-expr-complex.r1.stderr | 6 +- .../mgca/array-expr-complex.r2.stderr | 6 +- .../mgca/array-expr-complex.r3.stderr | 6 +- .../const-generics/mgca/array-expr-complex.rs | 6 +- .../mgca/array_expr_arg_complex.rs | 4 +- .../mgca/array_expr_arg_complex.stderr | 12 +- ...non-const-def-id-on-const-arg-with-anon.rs | 11 + .../mgca/bad-const-arg-fn-154539.rs | 5 +- .../mgca/bad-const-arg-fn-154539.stderr | 8 +- .../mgca/bad-direct-const-arg.rs | 8 + .../mgca/bad-direct-const-arg.stderr | 14 + .../mgca/direct-const-arg-feature-gate.rs | 5 + .../mgca/direct-const-arg-feature-gate.stderr | 28 ++ .../mgca/direct-const-arg-multiple-exprs.rs | 5 + .../direct-const-arg-multiple-exprs.stderr | 8 + ...rapped-path-not-accidentally-stabilized.rs | 15 + ...ed-path-not-accidentally-stabilized.stderr | 11 + .../mgca/explicit_anon_consts.rs | 16 +- .../mgca/explicit_anon_consts.stderr | 36 +-- ...ixed-direct-anon-expression-diagnostics.rs | 17 + ...-direct-anon-expression-diagnostics.stderr | 16 + ...lized-direct-const-with-anon-const-body.rs | 16 + .../mgca/tuple_ctor_complex_args.rs | 2 +- .../mgca/tuple_ctor_complex_args.stderr | 6 +- .../mgca/tuple_expr_arg_complex.rs | 8 +- .../mgca/tuple_expr_arg_complex.stderr | 24 +- ...type-const-free-anon-const-mismatch.stderr | 2 +- ...const-free-value-type-mismatch.next.stderr | 2 +- ...t-inherent-value-type-mismatch.next.stderr | 2 +- ...type-const-value-type-mismatch.next.stderr | 2 +- ...ems-before-lowering-ices.ice_155125.stderr | 7 +- ...ems-before-lowering-ices.ice_155164.stderr | 7 +- .../hir-crate-items-before-lowering-ices.rs | 8 +- .../inside-const-body-ice-155300.rs | 5 +- .../inside-const-body-ice-155300.stderr | 13 +- 73 files changed, 672 insertions(+), 359 deletions(-) create mode 100644 compiler/rustc_builtin_macros/src/direct_const_arg.rs create mode 100644 src/tools/rustfmt/tests/source/direct_const_arg.rs create mode 100644 src/tools/rustfmt/tests/target/direct_const_arg.rs create mode 100644 tests/pretty/direct-const-arg.pp create mode 100644 tests/pretty/direct-const-arg.rs create mode 100644 tests/ui/const-generics/mgca/auxiliary/anon-const-def-id-on-const-arg-with-anon.rs create mode 100644 tests/ui/const-generics/mgca/bad-direct-const-arg.rs create mode 100644 tests/ui/const-generics/mgca/bad-direct-const-arg.stderr create mode 100644 tests/ui/const-generics/mgca/direct-const-arg-feature-gate.rs create mode 100644 tests/ui/const-generics/mgca/direct-const-arg-feature-gate.stderr create mode 100644 tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.rs create mode 100644 tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.stderr create mode 100644 tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.rs create mode 100644 tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.stderr create mode 100644 tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.rs create mode 100644 tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.stderr create mode 100644 tests/ui/const-generics/mgca/serialized-direct-const-with-anon-const-body.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 632f138a4c798..480c3304813d9 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1378,15 +1378,6 @@ pub enum UnsafeSource { UserProvided, } -/// Track whether under `feature(min_generic_const_args)` this anon const -/// was explicitly disambiguated as an anon const or not through the use of -/// `const { ... }` syntax. -#[derive(Clone, PartialEq, Encodable, Decodable, Debug, Copy, Walkable)] -pub enum MgcaDisambiguation { - AnonConst, - Direct, -} - /// A constant (expression) that's not an item or associated item, /// but needs its own `DefId` for type-checking, const-eval, etc. /// These are usually found nested inside types (e.g., array lengths) @@ -1396,7 +1387,6 @@ pub enum MgcaDisambiguation { pub struct AnonConst { pub id: NodeId, pub value: Box, - pub mgca_disambiguation: MgcaDisambiguation, } /// An expression. @@ -1627,6 +1617,7 @@ impl Expr { | ExprKind::UnsafeBinderCast(..) | ExprKind::While(..) | ExprKind::Yield(YieldKind::Postfix(..)) + | ExprKind::DirectConstArg(..) | ExprKind::Err(_) | ExprKind::Dummy => prefix_attrs_precedence(&self.attrs), } @@ -1920,6 +1911,9 @@ pub enum ExprKind { UnsafeBinderCast(UnsafeBinderCastKind, Box, Option>), + /// An mGCA `direct_const_arg!()` expression. + DirectConstArg(Box), + /// Placeholder for an expression that wasn't syntactically well formed in some way. Err(ErrorGuaranteed), @@ -2566,6 +2560,8 @@ pub enum TyKind { FieldOf(Box, Option, Ident), /// A view of a type. `T.{ field_1, field_2 }`. View(Box, #[visitable(ignore)] ThinVec), + /// An mGCA `direct_const_arg!()` expression. + DirectConstArg(Box), /// Sometimes we need a dummy value when no error has occurred. Dummy, /// Placeholder for a kind that has failed to be defined. diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 56f96f9a8a279..0c2218e557f23 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -155,6 +155,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { | Yeet(..) | Yield(..) | UnsafeBinderCast(..) + | DirectConstArg(..) | Err(..) | Dummy => return false, } @@ -240,6 +241,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { | Try(_) | Yeet(None) | UnsafeBinderCast(..) + | DirectConstArg(..) | Err(_) | Dummy => { break None; @@ -301,9 +303,10 @@ fn type_trailing_braced_mac_call(mut ty: &ast::Ty) -> Option<&ast::MacCall> { | ast::TyKind::CVarArgs | ast::TyKind::Pat(..) | ast::TyKind::FieldOf(..) + | ast::TyKind::View(..) + | ast::TyKind::DirectConstArg(..) | ast::TyKind::Dummy - | ast::TyKind::Err(..) - | ast::TyKind::View(..) => break None, + | ast::TyKind::Err(..) => break None, } } } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index fb4e76321d150..e25c1a0b31937 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -418,7 +418,6 @@ macro_rules! common_visitor_and_walkers { UnsafeBinderCastKind, BinOpKind, BlockCheckMode, - MgcaDisambiguation, BorrowKind, BoundAsyncness, BoundConstness, @@ -1074,6 +1073,8 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, bytes), ExprKind::UnsafeBinderCast(kind, expr, ty) => visit_visitable!($($mut)? vis, kind, expr, ty), + ExprKind::DirectConstArg(expr) => + visit_visitable!($($mut)? vis, expr), ExprKind::Err(_guar) => {} ExprKind::Dummy => {} } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index f66d1ac16c907..4ed23e032d234 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -498,6 +498,18 @@ impl<'hir> LoweringContext<'_, 'hir> { } ExprKind::MacCall(_) => panic!("{:?} shouldn't exist here", e.span), + + ExprKind::DirectConstArg(_) => { + let e = self + .tcx + .dcx() + .struct_span_err( + e.span, + "expected expression, found `direct_const_arg!()` constant", + ) + .emit(); + hir::ExprKind::Err(e) + } }; hir::Expr { hir_id: expr_hir_id, kind, span } @@ -599,11 +611,7 @@ impl<'hir> LoweringContext<'_, 'hir> { arg }; - let anon_const = AnonConst { - id: node_id, - value: const_value, - mgca_disambiguation: MgcaDisambiguation::AnonConst, - }; + let anon_const = AnonConst { id: node_id, value: const_value }; generic_args.push(AngleBracketedArg::Arg(GenericArg::Const(anon_const))); } else { real_args.push(arg); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index dc1acade85ed5..4963a38ddfd45 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1478,6 +1478,16 @@ impl<'hir> LoweringContext<'_, 'hir> { } } } + TyKind::DirectConstArg(expr) + if self.tcx.features().min_generic_const_args() => + { + let ct = match self.can_lower_expr_to_const_arg_direct(expr) { + Ok(()) => self.lower_expr_to_const_arg_direct(expr, None), + Err(e) => e.emit(self), + }; + let ct = self.arena.alloc(ct); + return GenericArg::Const(ct.try_as_ambig_ct().unwrap()); + } _ => {} } GenericArg::Type(self.lower_ty_alloc(ty, itctx).try_as_ambig_ty().unwrap()) @@ -1754,6 +1764,14 @@ impl<'hir> LoweringContext<'_, 'hir> { // FIXME(scrabsha): lower view types to HIR. return self.lower_ty(ty, itctx); } + TyKind::DirectConstArg(_) => { + let e = self + .tcx + .dcx() + .struct_span_err(t.span, "expected type, found `direct_const_arg!()` constant") + .emit(); + hir::TyKind::Err(e) + } TyKind::Dummy => panic!("`TyKind::Dummy` should never be lowered"), }; @@ -2672,23 +2690,86 @@ impl<'hir> LoweringContext<'_, 'hir> { } #[instrument(level = "debug", skip(self), ret)] - fn lower_expr_to_const_arg_direct(&mut self, expr: &Expr) -> hir::ConstArg<'hir> { - let span = self.lower_span(expr.span); - - let overly_complex_const = |this: &mut Self| { - let msg = "complex const arguments must be placed inside of a `const` block"; - let e = if expr::WillCreateDefIdsVisitor.visit_expr(expr).is_break() { - // FIXME(mgca): make this non-fatal once we have a better way to handle - // nested items in const args - // Issue: https://github.com/rust-lang/rust/issues/154539 - this.dcx().struct_span_fatal(expr.span, msg).emit() - } else { - this.dcx().struct_span_err(expr.span, msg).emit() - }; + fn can_lower_expr_to_const_arg_direct( + &mut self, + expr: &Expr, + ) -> Result<(), UnrepresentableConstArgError> { + let is_mgca = self.tcx.features().min_generic_const_args(); + // Note the only stable case is currently ExprKind::Path. All others have an is_mgca guard. + match &expr.kind { + ExprKind::Call(func, args) + if is_mgca && let ExprKind::Path(_qself, _path) = &func.kind => + { + for arg in args { + self.can_lower_expr_to_const_arg_direct(arg)?; + } + Ok(()) + } + ExprKind::Tup(exprs) if is_mgca => { + for expr in exprs { + self.can_lower_expr_to_const_arg_direct(expr)?; + } + Ok(()) + } + ExprKind::Path(qself, path) + if is_mgca + || path.is_potential_trivial_const_arg() + && matches!( + self.get_partial_res(expr.id) + .and_then(|partial_res| partial_res.full_res()), + Some(Res::Def(DefKind::ConstParam, _)) + ) => + { + Ok(()) + } + ExprKind::Struct(se) if is_mgca => { + for f in &se.fields { + self.can_lower_expr_to_const_arg_direct(&f.expr)?; + } + Ok(()) + } + ExprKind::Array(elements) if is_mgca => { + for element in elements { + self.can_lower_expr_to_const_arg_direct(element)?; + } + Ok(()) + } + ExprKind::Underscore if is_mgca => Ok(()), + ExprKind::Block(block, _) + if is_mgca + && let [stmt] = block.stmts.as_slice() + && let StmtKind::Expr(expr) = &stmt.kind => + { + self.can_lower_expr_to_const_arg_direct(expr) + } + ExprKind::Lit(literal) if is_mgca => Ok(()), + ExprKind::Unary(UnOp::Neg, inner_expr) + if is_mgca && let ExprKind::Lit(_) = &inner_expr.kind => + { + Ok(()) + } + ExprKind::ConstBlock(anon) if is_mgca => Ok(()), + ExprKind::DirectConstArg(expr) if is_mgca => { + // Always report this as able to be represented directly. If it turns out not to be, + // `lower_expr_to_const_arg_direct` will report an error. + Ok(()) + } + _ => Err(UnrepresentableConstArgError::new(expr)), + } + } - ConstArg { hir_id: this.next_id(), kind: hir::ConstArgKind::Error(e), span } - }; + /// It is not allowed to call this function without checking can_lower_expr_to_const_arg_direct + /// first, as we assume all feature gates/etc. have been checked already. + #[instrument(level = "debug", skip(self), ret)] + fn lower_expr_to_const_arg_direct( + &mut self, + expr: &Expr, + id_override: Option, + ) -> hir::ConstArg<'hir> { + debug_assert!(self.can_lower_expr_to_const_arg_direct(expr).is_ok()); + let span = self.lower_span(expr.span); + let node_id = id_override.unwrap_or(expr.id); match &expr.kind { ExprKind::Call(func, args) if let ExprKind::Path(qself, path) = &func.kind => { let qpath = self.lower_qpath( @@ -2702,23 +2783,27 @@ impl<'hir> LoweringContext<'_, 'hir> { ); let lowered_args = self.arena.alloc_from_iter(args.iter().map(|arg| { - let const_arg = self.lower_expr_to_const_arg_direct(arg); + let const_arg = self.lower_expr_to_const_arg_direct(arg, None); &*self.arena.alloc(const_arg) })); ConstArg { - hir_id: self.next_id(), + hir_id: self.lower_node_id(node_id), kind: hir::ConstArgKind::TupleCall(qpath, lowered_args), span, } } ExprKind::Tup(exprs) => { let exprs = self.arena.alloc_from_iter(exprs.iter().map(|expr| { - let expr = self.lower_expr_to_const_arg_direct(&expr); + let expr = self.lower_expr_to_const_arg_direct(expr, None); &*self.arena.alloc(expr) })); - ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Tup(exprs), span } + ConstArg { + hir_id: self.lower_node_id(node_id), + kind: hir::ConstArgKind::Tup(exprs), + span, + } } ExprKind::Path(qself, path) => { let qpath = self.lower_qpath( @@ -2732,7 +2817,11 @@ impl<'hir> LoweringContext<'_, 'hir> { None, ); - ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Path(qpath), span } + ConstArg { + hir_id: self.lower_node_id(node_id), + kind: hir::ConstArgKind::Path(qpath), + span, + } } ExprKind::Struct(se) => { let path = self.lower_qpath( @@ -2754,7 +2843,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // then go unused as the `Target::ExprField` is not actually // corresponding to `Node::ExprField`. self.lower_attrs(hir_id, &f.attrs, f.span, Target::ExprField); - let expr = self.lower_expr_to_const_arg_direct(&f.expr); + let expr = self.lower_expr_to_const_arg_direct(&f.expr, None); &*self.arena.alloc(hir::ConstArgExprField { hir_id, @@ -2765,14 +2854,14 @@ impl<'hir> LoweringContext<'_, 'hir> { })); ConstArg { - hir_id: self.next_id(), + hir_id: self.lower_node_id(node_id), kind: hir::ConstArgKind::Struct(path, fields), span, } } ExprKind::Array(elements) => { let lowered_elems = self.arena.alloc_from_iter(elements.iter().map(|element| { - let const_arg = self.lower_expr_to_const_arg_direct(element); + let const_arg = self.lower_expr_to_const_arg_direct(element, None); &*self.arena.alloc(const_arg) })); let array_expr = self.arena.alloc(hir::ConstArgArrayExpr { @@ -2781,31 +2870,28 @@ impl<'hir> LoweringContext<'_, 'hir> { }); ConstArg { - hir_id: self.next_id(), + hir_id: self.lower_node_id(node_id), kind: hir::ConstArgKind::Array(array_expr), span, } } ExprKind::Underscore => ConstArg { - hir_id: self.lower_node_id(expr.id), + hir_id: self.lower_node_id(node_id), kind: hir::ConstArgKind::Infer(()), span, }, - ExprKind::Block(block, _) => { + ExprKind::Block(block, _) if let [stmt] = block.stmts.as_slice() - && let StmtKind::Expr(expr) = &stmt.kind - { - return self.lower_expr_to_const_arg_direct(expr); - } - - overly_complex_const(self) + && let StmtKind::Expr(expr) = &stmt.kind => + { + return self.lower_expr_to_const_arg_direct(expr, id_override); } ExprKind::Lit(literal) => { let span = self.lower_span(expr.span); let literal = self.lower_lit(literal, span); ConstArg { - hir_id: self.lower_node_id(expr.id), + hir_id: self.lower_node_id(node_id), kind: hir::ConstArgKind::Literal { lit: literal.node, negated: false }, span, } @@ -2816,29 +2902,45 @@ impl<'hir> LoweringContext<'_, 'hir> { let span = self.lower_span(expr.span); let literal = self.lower_lit(literal, span); - if !matches!(literal.node, LitKind::Int(..)) { + let kind = if !matches!(literal.node, LitKind::Int(..)) { let err = self.dcx().struct_span_err(expr.span, "negated literal must be an integer"); - - return ConstArg { - hir_id: self.next_id(), - kind: hir::ConstArgKind::Error(err.emit()), - span, - }; - } - - ConstArg { - hir_id: self.lower_node_id(expr.id), - kind: hir::ConstArgKind::Literal { lit: literal.node, negated: true }, - span, - } + hir::ConstArgKind::Error(err.emit()) + } else { + hir::ConstArgKind::Literal { lit: literal.node, negated: true } + }; + ConstArg { hir_id: self.lower_node_id(node_id), kind, span } } ExprKind::ConstBlock(anon_const) => { + // Do not use lower_anon_const_to_const_arg, as that attempts to represent the body + // directly. Instead, force an anon const. let def_id = self.local_def_id(anon_const.id); assert_eq!(DefKind::InlineConst, self.tcx.def_kind(def_id)); - self.lower_anon_const_to_const_arg(anon_const, span) + let lowered_anon = self.lower_anon_const_to_anon_const(anon_const, span); + ConstArg { + hir_id: self.lower_node_id(node_id), + kind: hir::ConstArgKind::Anon(lowered_anon), + span, + } + } + ExprKind::DirectConstArg(expr) => { + // `can_lower_expr_to_const_arg_direct` always returns success upon encountering a + // ExprKind::DirectConstArg, which effectively forces the expression to be lowered + // as a direct arg. If it actually turns out to not be possible, emit an error + // instead. + match self.can_lower_expr_to_const_arg_direct(expr) { + Ok(()) => self.lower_expr_to_const_arg_direct(expr, id_override), + Err(err) => err.emit(self), + } + } + _ => { + span_bug!( + expr.span, + "lower_expr_to_const_arg_direct encountered an unlowerable expression, either \ + can_lower_expr_to_const_arg_direct returned Ok() on something it shouldn't \ + have, or you forgot to check can_lower_expr_to_const_arg_direct first" + ); } - _ => overly_complex_const(self), } } @@ -2857,67 +2959,23 @@ impl<'hir> LoweringContext<'_, 'hir> { anon: &AnonConst, span: Span, ) -> hir::ConstArg<'hir> { - let tcx = self.tcx; - - // We cannot change parsing depending on feature gates available, - // we can only require feature gates to be active as a delayed check. - // Thus we just parse anon consts generally and make the real decision - // making in ast lowering. - // FIXME(min_generic_const_args): revisit once stable - if tcx.features().min_generic_const_args() { - return match anon.mgca_disambiguation { - MgcaDisambiguation::AnonConst => { - let lowered_anon = self.lower_anon_const_to_anon_const(anon, span); - ConstArg { - hir_id: self.next_id(), - kind: hir::ConstArgKind::Anon(lowered_anon), - span: lowered_anon.span, - } - } - MgcaDisambiguation::Direct => self.lower_expr_to_const_arg_direct(&anon.value), - }; - } - - // Unwrap a block, so that e.g. `{ P }` is recognised as a parameter. Const arguments - // currently have to be wrapped in curly brackets, so it's necessary to special-case. - let expr = if let ExprKind::Block(block, _) = &anon.value.kind - && let [stmt] = block.stmts.as_slice() - && let StmtKind::Expr(expr) = &stmt.kind - && let ExprKind::Path(..) = &expr.kind - { - expr - } else { + // Stable only allows one nesting of blocks for directly represented paths. mGCA allows + // arbitrarily many, and are handled inside lower_expr_to_const_arg_direct for consistency. + let expr = if self.tcx.features().min_generic_const_args() { &anon.value + } else { + anon.value.maybe_unwrap_block() }; - let maybe_res = - self.get_partial_res(expr.id).and_then(|partial_res| partial_res.full_res()); - if let ExprKind::Path(qself, path) = &expr.kind - && path.is_potential_trivial_const_arg() - && matches!(maybe_res, Some(Res::Def(DefKind::ConstParam, _))) - { - let qpath = self.lower_qpath( - expr.id, - qself, - path, - ParamMode::Explicit, - AllowReturnTypeNotation::No, - ImplTraitContext::Disallowed(ImplTraitPosition::Path), - None, - ); - - return ConstArg { - hir_id: self.lower_node_id(anon.id), - kind: hir::ConstArgKind::Path(qpath), - span: self.lower_span(expr.span), - }; + if self.can_lower_expr_to_const_arg_direct(expr).is_ok() { + return self.lower_expr_to_const_arg_direct(expr, Some(anon.id)); } let lowered_anon = self.lower_anon_const_to_anon_const(anon, anon.value.span); ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Anon(lowered_anon), - span: self.lower_span(expr.span), + span: self.lower_span(anon.value.span), } } @@ -3213,3 +3271,36 @@ impl<'hir> GenericArgsCtor<'hir> { this.arena.alloc(ga) } } + +#[derive(Debug)] +struct UnrepresentableConstArgError { + span: Span, + will_create_def_ids: bool, +} + +impl UnrepresentableConstArgError { + fn new(expr: &Expr) -> Self { + Self { + span: expr.span, + will_create_def_ids: expr::WillCreateDefIdsVisitor.visit_expr(expr).is_break(), + } + } + + fn emit<'hir>(self, lowering_context: &mut LoweringContext<'_, 'hir>) -> ConstArg<'hir> { + let msg = "complex const arguments must be placed inside of a `const` block"; + let e = if self.will_create_def_ids { + // FIXME(mgca): make this non-fatal once we have a better way to handle + // nested items in const args + // Issue: https://github.com/rust-lang/rust/issues/154539 + lowering_context.dcx().struct_span_fatal(self.span, msg).emit() + } else { + lowering_context.dcx().struct_span_err(self.span, msg).emit() + }; + + ConstArg { + hir_id: lowering_context.next_id(), + kind: hir::ConstArgKind::Error(e), + span: self.span, + } + } +} diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 106606877e110..bfca18a42635e 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1459,6 +1459,12 @@ impl<'a> State<'a> { self.print_type(ty); self.print_view(fields); } + ast::TyKind::DirectConstArg(expr) => { + self.word_nbsp("core::direct_const_arg!"); + self.popen(); + self.print_expr(expr, FixupContext::default()); + self.pclose(); + } } self.end(ib); } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 176e91e544ec5..4f3281641359d 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -883,6 +883,12 @@ impl<'a> State<'a> { self.word("/*DUMMY*/"); self.pclose(); } + ast::ExprKind::DirectConstArg(expr) => { + self.word_nbsp("core::direct_const_arg!"); + self.popen(); + self.print_expr(expr, FixupContext::default()); + self.pclose() + } } self.ann.post(self, AnnNode::Expr(expr)); diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index f15acc154baf3..1bc2bc8342559 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -323,6 +323,7 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Yeet(_) | ExprKind::Become(_) | ExprKind::Yield(_) + | ExprKind::DirectConstArg(_) | ExprKind::UnsafeBinderCast(..) => {} } } diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 311a24280cfb7..bd5bf3a687d92 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -16,7 +16,7 @@ mod llvm_enzyme { use rustc_ast::{ self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AssocItemKind, BindingMode, FnRetTy, FnSig, GenericArg, GenericArgs, GenericParamKind, Generics, ItemKind, - MetaItemInner, MgcaDisambiguation, PatKind, Path, PathSegment, TyKind, Visibility, + MetaItemInner, PatKind, Path, PathSegment, TyKind, Visibility, }; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_hir::attrs::RustcAutodiff; @@ -602,11 +602,7 @@ mod llvm_enzyme { } GenericParamKind::Const { .. } => { let expr = ecx.expr_path(ast::Path::from_ident(p.ident)); - let anon_const = AnonConst { - id: ast::DUMMY_NODE_ID, - value: expr, - mgca_disambiguation: MgcaDisambiguation::Direct, - }; + let anon_const = AnonConst { id: ast::DUMMY_NODE_ID, value: expr }; Some(AngleBracketedArg::Arg(GenericArg::Const(anon_const))) } GenericParamKind::Lifetime { .. } => None, @@ -861,7 +857,6 @@ mod llvm_enzyme { let anon_const = rustc_ast::AnonConst { id: ast::DUMMY_NODE_ID, value: ecx.expr_usize(span, 1 + x.width as usize), - mgca_disambiguation: MgcaDisambiguation::Direct, }; TyKind::Array(ty.clone(), anon_const) }; @@ -876,7 +871,6 @@ mod llvm_enzyme { let anon_const = rustc_ast::AnonConst { id: ast::DUMMY_NODE_ID, value: ecx.expr_usize(span, x.width as usize), - mgca_disambiguation: MgcaDisambiguation::Direct, }; let kind = TyKind::Array(ty.clone(), anon_const); let ty = diff --git a/compiler/rustc_builtin_macros/src/direct_const_arg.rs b/compiler/rustc_builtin_macros/src/direct_const_arg.rs new file mode 100644 index 0000000000000..51c169134e03f --- /dev/null +++ b/compiler/rustc_builtin_macros/src/direct_const_arg.rs @@ -0,0 +1,39 @@ +use rustc_ast::ast; +use rustc_ast::tokenstream::TokenStream; +use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; +use rustc_span::Span; + +use crate::util::get_single_expr_from_tts; + +pub(crate) fn expand<'cx>( + cx: &'cx mut ExtCtxt<'_>, + span: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let ExpandResult::Ready(expr) = get_single_expr_from_tts(cx, span, tts, "direct_const_arg!") + else { + return ExpandResult::Retry(()); + }; + let expr = match expr { + Ok(expr) => expr, + Err(err) => return ExpandResult::Ready(DummyResult::any(span, err)), + }; + + let id = ast::DUMMY_NODE_ID; + ExpandResult::Ready(Box::new(base::MacEager { + expr: Some(Box::new(ast::Expr { + id, + kind: ast::ExprKind::DirectConstArg(expr.clone()), + span, + attrs: Default::default(), + tokens: None, + })), + ty: Some(Box::new(ast::Ty { + id, + kind: ast::TyKind::DirectConstArg(expr), + span, + tokens: None, + })), + ..Default::default() + })) +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index bd99b269ef5b6..78bf7d97bd7b8 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -34,6 +34,7 @@ mod define_opaque; mod derive; mod deriving; mod diagnostics; +mod direct_const_arg; mod edition_panic; mod eii; mod env; @@ -81,6 +82,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { concat_bytes: concat_bytes::expand_concat_bytes, const_format_args: format::expand_format_args, core_panic: edition_panic::expand_panic, + direct_const_arg: direct_const_arg::expand, env: env::expand_env, file: source_util::expand_file, format_args: format::expand_format_args, diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs index 53ab3fcd9b34b..065ba9f6a2096 100644 --- a/compiler/rustc_builtin_macros/src/pattern_type.rs +++ b/compiler/rustc_builtin_macros/src/pattern_type.rs @@ -1,5 +1,5 @@ use rustc_ast::tokenstream::TokenStream; -use rustc_ast::{AnonConst, DUMMY_NODE_ID, MgcaDisambiguation, Ty, TyPat, TyPatKind, ast, token}; +use rustc_ast::{AnonConst, DUMMY_NODE_ID, Ty, TyPat, TyPatKind, ast, token}; use rustc_errors::PResult; use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; use rustc_parse::exp; @@ -60,20 +60,8 @@ fn ty_pat(kind: TyPatKind, span: Span) -> TyPat { fn pat_to_ty_pat(cx: &mut ExtCtxt<'_>, pat: ast::Pat) -> TyPat { let kind = match pat.kind { ast::PatKind::Range(start, end, include_end) => TyPatKind::Range( - start.map(|value| { - Box::new(AnonConst { - id: DUMMY_NODE_ID, - value, - mgca_disambiguation: MgcaDisambiguation::Direct, - }) - }), - end.map(|value| { - Box::new(AnonConst { - id: DUMMY_NODE_ID, - value, - mgca_disambiguation: MgcaDisambiguation::Direct, - }) - }), + start.map(|value| Box::new(AnonConst { id: DUMMY_NODE_ID, value })), + end.map(|value| Box::new(AnonConst { id: DUMMY_NODE_ID, value })), include_end, ), ast::PatKind::Or(variants) => { diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 01886a97f55a2..f3792d4d45235 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -2,8 +2,8 @@ use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::TokenStream; use rustc_ast::util::literal; use rustc_ast::{ - self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, - MgcaDisambiguation, PatKind, UnOp, attr, token, tokenstream, + self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, + UnOp, attr, token, tokenstream, }; use rustc_span::{DUMMY_SP, Ident, Span, Spanned, Symbol, kw, sym}; use thin_vec::{ThinVec, thin_vec}; @@ -100,7 +100,6 @@ impl<'a> ExtCtxt<'a> { attrs: AttrVec::new(), tokens: None, }), - mgca_disambiguation: MgcaDisambiguation::Direct, } } diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index 811ed83e0bf48..d6a5db4c22f24 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -214,6 +214,17 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { "synthetic HIR should have its `generics_of` explicitly fed" ), + Node::ConstArg(..) => { + // These can show up in mGCA when representing "direct" const arguments. The + // DefCollector cannot know whether an anon const will be represented by an actual HIR + // Node::AnonConst, or whether it will be represented directly, so it must generate a + // DefId. If it ends up being direct, this DefId is then attached to the top-level + // ConstArg, which is what we are seeing here. + debug_assert!(tcx.features().min_generic_const_args()); + // Forward to the real parent. + Some(tcx.local_parent(def_id)) + } + _ => span_bug!(tcx.def_span(def_id), "generics_of: unexpected node kind {node:?}"), }; diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 16a5a6c8877a5..6a7942a2bad29 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1108,7 +1108,7 @@ fn should_encode_mir( // instance_mir uses mir_for_ctfe rather than optimized_mir for constructors DefKind::Ctor(_, _) => (true, false), // Constants - DefKind::AnonConst { .. } + DefKind::AnonConst | DefKind::InlineConst | DefKind::AssocConst { .. } | DefKind::Const { .. } => (true, false), @@ -1438,27 +1438,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // for trivial const arguments which are directly lowered to // `ConstArgKind::Path`. We never actually access this `DefId` // anywhere so we don't need to encode it for other crates. + // FIXME(mgca): This probably isn't true, they probably are accessed, but, test case? if def_kind == DefKind::AnonConst - && match tcx.hir_node_by_def_id(local_id) { - hir::Node::ConstArg(hir::ConstArg { kind, .. }) => match kind { - // Skip encoding defs for these as they should not have had a `DefId` created - hir::ConstArgKind::Error(..) - | hir::ConstArgKind::Struct(..) - | hir::ConstArgKind::Array(..) - | hir::ConstArgKind::TupleCall(..) - | hir::ConstArgKind::Tup(..) - | hir::ConstArgKind::Path(..) - | hir::ConstArgKind::Literal { .. } - | hir::ConstArgKind::Infer(..) => true, - hir::ConstArgKind::Anon(..) => false, - }, - _ => false, - } + && matches!(tcx.hir_node_by_def_id(local_id), hir::Node::ConstArg(_)) { - // MGCA doesn't have unnecessary DefIds - if !tcx.features().min_generic_const_args() { - continue; - } + continue; } if def_kind == DefKind::Field diff --git a/compiler/rustc_parse/src/parser/asm.rs b/compiler/rustc_parse/src/parser/asm.rs index 3fab234adaad4..11460e775d36d 100644 --- a/compiler/rustc_parse/src/parser/asm.rs +++ b/compiler/rustc_parse/src/parser/asm.rs @@ -1,4 +1,4 @@ -use rustc_ast::{self as ast, AsmMacro, MgcaDisambiguation}; +use rustc_ast::{self as ast, AsmMacro}; use rustc_span::{Span, Symbol, kw}; use super::{ExpKeywordPair, ForceCollect, IdentIsRaw, Trailing, UsePreAttrPos}; @@ -149,7 +149,7 @@ fn parse_asm_operand<'a>( let block = p.parse_block()?; ast::InlineAsmOperand::Label { block } } else if p.eat_keyword(exp!(Const)) { - let anon_const = p.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?; + let anon_const = p.parse_expr_anon_const()?; ast::InlineAsmOperand::Const { anon_const } } else if p.eat_keyword(exp!(Sym)) { let expr = p.parse_expr()?; diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 28e2841b9f1fe..a91ee634f3d47 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -7,7 +7,7 @@ use rustc_ast::util::parser::AssocOp; use rustc_ast::{ self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingMode, Block, BlockCheckMode, Expr, ExprKind, GenericArg, GenericArgs, Generics, Item, ItemKind, - MgcaDisambiguation, Param, Pat, PatKind, Path, PathSegment, QSelf, Recovered, Ty, TyKind, + Param, Pat, PatKind, Path, PathSegment, QSelf, Recovered, Ty, TyKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; @@ -2585,11 +2585,7 @@ impl<'a> Parser<'a> { self.dcx().emit_err(UnexpectedConstParamDeclaration { span: param.span(), sugg }); let value = self.mk_expr_err(param.span(), guar); - Some(GenericArg::Const(AnonConst { - id: ast::DUMMY_NODE_ID, - value, - mgca_disambiguation: MgcaDisambiguation::Direct, - })) + Some(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })) } pub(super) fn recover_const_param_declaration( @@ -2673,11 +2669,7 @@ impl<'a> Parser<'a> { ); let guar = err.emit(); let value = self.mk_expr_err(start.to(expr.span), guar); - return Ok(GenericArg::Const(AnonConst { - id: ast::DUMMY_NODE_ID, - value, - mgca_disambiguation: MgcaDisambiguation::Direct, - })); + return Ok(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })); } else if snapshot.token == token::Colon && expr.span.lo() == snapshot.token.span.hi() && matches!(expr.kind, ExprKind::Path(..)) @@ -2746,11 +2738,7 @@ impl<'a> Parser<'a> { ); let guar = err.emit(); let value = self.mk_expr_err(span, guar); - GenericArg::Const(AnonConst { - id: ast::DUMMY_NODE_ID, - value, - mgca_disambiguation: MgcaDisambiguation::Direct, - }) + GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }) } /// Some special error handling for the "top-level" patterns in a match arm, diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index df1877b82cb93..e151899a57228 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -15,8 +15,8 @@ use rustc_ast::visit::{Visitor, walk_expr}; use rustc_ast::{ self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind, BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, - FnRetTy, Guard, Label, MacCall, MetaItemLit, MgcaDisambiguation, Movability, Param, - RangeLimits, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind, YieldKind, + FnRetTy, Guard, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, + TyKind, UnOp, UnsafeBinderCastKind, YieldKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -83,15 +83,8 @@ impl<'a> Parser<'a> { ) } - pub fn parse_expr_anon_const( - &mut self, - mgca_disambiguation: impl FnOnce(&Self, &Expr) -> MgcaDisambiguation, - ) -> PResult<'a, AnonConst> { - self.parse_expr().map(|value| AnonConst { - id: DUMMY_NODE_ID, - mgca_disambiguation: mgca_disambiguation(self, &value), - value, - }) + pub fn parse_expr_anon_const(&mut self) -> PResult<'a, AnonConst> { + self.parse_expr().map(|value| AnonConst { id: DUMMY_NODE_ID, value }) } fn parse_expr_catch_underscore( @@ -1672,7 +1665,7 @@ impl<'a> Parser<'a> { let first_expr = self.parse_expr()?; if self.eat(exp!(Semi)) { // Repeating array syntax: `[ 0; 512 ]` - let count = self.parse_expr_anon_const(|_, _| MgcaDisambiguation::Direct)?; + let count = self.parse_expr_anon_const()?; self.expect(close)?; ExprKind::Repeat(first_expr, count) } else if self.eat(exp!(Comma)) { @@ -4508,6 +4501,7 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::IncludedBytes(_) | ExprKind::FormatArgs(_) | ExprKind::Err(_) + | ExprKind::DirectConstArg(_) | ExprKind::Dummy => { // These would forbid any let expressions they contain already. } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 98b2ed1642582..90bfc9ece6b18 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1696,9 +1696,9 @@ impl<'a> Parser<'a> { if self.may_recover() { self.parse_where_clause()? } else { WhereClause::default() }; let rhs = match (self.eat(exp!(Eq)), const_arg) { - (true, true) => ConstItemRhsKind::TypeConst { - rhs: Some(self.parse_expr_anon_const(|_, _| MgcaDisambiguation::Direct)?), - }, + (true, true) => { + ConstItemRhsKind::TypeConst { rhs: Some(self.parse_expr_anon_const()?) } + } (true, false) => ConstItemRhsKind::Body { rhs: Some(self.parse_expr()?) }, (false, true) => ConstItemRhsKind::TypeConst { rhs: None }, (false, false) => ConstItemRhsKind::Body { rhs: None }, @@ -1918,11 +1918,8 @@ impl<'a> Parser<'a> { VariantData::Unit(DUMMY_NODE_ID) }; - let disr_expr = if this.eat(exp!(Eq)) { - Some(this.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?) - } else { - None - }; + let disr_expr = + if this.eat(exp!(Eq)) { Some(this.parse_expr_anon_const()?) } else { None }; let span = vlo.to(this.prev_token.span); if ident.name == kw::Underscore { @@ -2143,7 +2140,7 @@ impl<'a> Parser<'a> { if p.token == token::Eq { let mut snapshot = p.create_snapshot_for_diagnostic(); snapshot.bump(); - match snapshot.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst) { + match snapshot.parse_expr_anon_const() { Ok(const_expr) => { let sp = ty.span.shrink_to_hi().to(const_expr.value.span); p.psess.gated_spans.gate(sym::default_field_values, sp); @@ -2372,7 +2369,7 @@ impl<'a> Parser<'a> { } let default = if self.token == token::Eq { self.bump(); - let const_expr = self.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?; + let const_expr = self.parse_expr_anon_const()?; let sp = ty.span.shrink_to_hi().to(const_expr.value.span); self.psess.gated_spans.gate(sym::default_field_values, sp); Some(const_expr) diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 04a9b1ff212a7..8b57968413d3d 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -36,8 +36,8 @@ use rustc_ast::util::classify; use rustc_ast::{ self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, ByRef, Const, CoroutineKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, ImplRestriction, - MgcaDisambiguation, MutRestriction, Mutability, Recovered, RestrictionKind, Safety, StrLit, - Visibility, VisibilityKind, + MutRestriction, Mutability, Recovered, RestrictionKind, Safety, StrLit, Visibility, + VisibilityKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; @@ -1298,7 +1298,6 @@ impl<'a> Parser<'a> { let anon_const = AnonConst { id: DUMMY_NODE_ID, value: self.mk_expr(blk.span, ExprKind::Block(blk, None)), - mgca_disambiguation: MgcaDisambiguation::AnonConst, }; let blk_span = anon_const.value.span; let kind = if pat { diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index ea86c08915b31..d3839579d1311 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -4,8 +4,8 @@ use ast::token::IdentIsRaw; use rustc_ast::token::{self, MetaVarKind, Token, TokenKind}; use rustc_ast::{ self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AssocItemConstraint, - AssocItemConstraintKind, BlockCheckMode, GenericArg, GenericArgs, Generics, MgcaDisambiguation, - ParenthesizedArgs, Path, PathSegment, QSelf, + AssocItemConstraintKind, BlockCheckMode, GenericArg, GenericArgs, Generics, ParenthesizedArgs, + Path, PathSegment, QSelf, }; use rustc_errors::{Applicability, Diag, PResult}; use rustc_span::{BytePos, Ident, Span, kw, sym}; @@ -876,13 +876,12 @@ impl<'a> Parser<'a> { /// the caller. pub(super) fn parse_const_arg(&mut self) -> PResult<'a, AnonConst> { // Parse const argument. - let (value, mgca_disambiguation) = if self.token.kind == token::OpenBrace { - let value = self.parse_expr_block(None, self.token.span, BlockCheckMode::Default)?; - (value, MgcaDisambiguation::Direct) + let value = if self.token.kind == token::OpenBrace { + self.parse_expr_block(None, self.token.span, BlockCheckMode::Default)? } else { self.parse_unambiguous_unbraced_const_arg()? }; - Ok(AnonConst { id: ast::DUMMY_NODE_ID, value, mgca_disambiguation }) + Ok(AnonConst { id: ast::DUMMY_NODE_ID, value }) } /// Attempt to parse a const argument that has not been enclosed in braces. @@ -892,9 +891,7 @@ impl<'a> Parser<'a> { /// - Single-segment paths (i.e. standalone generic const parameters). /// All other expressions that can be parsed will emit an error suggesting the expression be /// wrapped in braces. - pub(super) fn parse_unambiguous_unbraced_const_arg( - &mut self, - ) -> PResult<'a, (Box, MgcaDisambiguation)> { + pub(super) fn parse_unambiguous_unbraced_const_arg(&mut self) -> PResult<'a, Box> { let start = self.token.span; let attrs = self.parse_outer_attributes()?; let (expr, _) = @@ -915,7 +912,7 @@ impl<'a> Parser<'a> { }); } - Ok((expr, MgcaDisambiguation::Direct)) + Ok(expr) } /// Parse a generic argument in a path segment. @@ -1019,11 +1016,7 @@ impl<'a> Parser<'a> { GenericArg::Type(_) => GenericArg::Type(self.mk_ty(attr_span, TyKind::Err(guar))), GenericArg::Const(_) => { let error_expr = self.mk_expr(attr_span, ExprKind::Err(guar)); - GenericArg::Const(AnonConst { - id: ast::DUMMY_NODE_ID, - value: error_expr, - mgca_disambiguation: MgcaDisambiguation::Direct, - }) + GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: error_expr }) } GenericArg::Lifetime(lt) => GenericArg::Lifetime(lt), })); diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 349c8d1bea9e8..7c91c15f34034 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -2,9 +2,9 @@ use rustc_ast::token::{self, IdentIsRaw, MetaVarKind, Token, TokenKind}; use rustc_ast::util::case::Case; use rustc_ast::{ self as ast, BoundAsyncness, BoundConstness, BoundPolarity, DUMMY_NODE_ID, FnPtrTy, FnRetTy, - GenericBound, GenericBounds, GenericParam, Generics, Lifetime, MacCall, MgcaDisambiguation, - MutTy, Mutability, Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, - TraitObjectSyntax, Ty, TyKind, UnsafeBinderTy, + GenericBound, GenericBounds, GenericParam, Generics, Lifetime, MacCall, MutTy, Mutability, + Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty, + TyKind, UnsafeBinderTy, }; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{Applicability, Diag, E0516, PResult}; @@ -660,7 +660,7 @@ impl<'a> Parser<'a> { }; let ty = if self.eat(exp!(Semi)) { - let mut length = self.parse_expr_anon_const(|_, _| MgcaDisambiguation::Direct)?; + let mut length = self.parse_expr_anon_const()?; if let Err(e) = self.expect(exp!(CloseBracket)) { // Try to recover from `X` when `X::` works @@ -704,7 +704,7 @@ impl<'a> Parser<'a> { // FIXME(mgca): recovery is broken for `const {` args // we first try to parse pattern like `[u8 5]` - let length = match self.parse_expr_anon_const(|_, _| MgcaDisambiguation::Direct) { + let length = match self.parse_expr_anon_const() { Ok(length) => length, Err(e) => { e.cancel(); @@ -794,7 +794,7 @@ impl<'a> Parser<'a> { /// an error type. fn parse_typeof_ty(&mut self, lo: Span) -> PResult<'a, TyKind> { self.expect(exp!(OpenParen))?; - let _expr = self.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?; + let _expr = self.parse_expr_anon_const()?; self.expect(exp!(CloseParen))?; let span = lo.to(self.prev_token.span); let guar = self diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index d2bcf0359342c..8a7104c175f39 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -660,7 +660,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { If, While, ForLoop, Loop, Match, Closure, Block, Await, Move, Use, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, - Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy + Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy, DirectConstArg ] ); ast_visit::walk_expr(self, e) @@ -688,9 +688,10 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { ImplicitSelf, MacCall, CVarArgs, - Dummy, FieldOf, View, + DirectConstArg, + Dummy, Err ] ); diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index de3f8c380fc44..81c6db6a9d6d7 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -425,26 +425,9 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { fn visit_anon_const(&mut self, constant: &'a AnonConst) { // Note that `visit_anon_const` is skipped for AnonConst nodes wrapped in an // ExprKind::ConstBlock - these are handled in visit_expr, and are DefKind::InlineConst. - - // `MgcaDisambiguation::Direct` is set even when MGCA is disabled, so - // to avoid affecting stable we have to feature gate the not creating - // anon consts - if !self.r.features.min_generic_const_args() { - let parent = self - .create_def(constant.id, None, DefKind::AnonConst, constant.value.span) - .def_id(); - return self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); - } - - match constant.mgca_disambiguation { - MgcaDisambiguation::Direct => visit::walk_anon_const(self, constant), - MgcaDisambiguation::AnonConst => { - let parent = self - .create_def(constant.id, None, DefKind::AnonConst, constant.value.span) - .def_id(); - self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); - } - }; + let parent = + self.create_def(constant.id, None, DefKind::AnonConst, constant.value.span).def_id(); + self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); } #[instrument(level = "debug", skip(self))] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index db2804a3f83bf..084241afef5b7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -822,6 +822,7 @@ symbols! { diagnostic_on_unmatched_args, dialect, direct, + direct_const_arg, discriminant_kind, discriminant_type, discriminant_value, diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 45b8b266a2671..e3785c92c8d0d 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -1073,6 +1073,20 @@ pub const trait Destruct: PointeeSized {} #[rustc_dyn_incompatible_trait] pub trait Tuple {} +/// Creates a new style directly represented const argument. +/// ```ignore (cannot test this from within core yet) +/// type const BAR: usize = N; +/// type const FOO: usize = direct!(BAR::); +/// ``` +#[rustc_builtin_macro(direct_const_arg)] +#[unstable(feature = "min_generic_const_args", issue = "132980")] +#[macro_export] +macro_rules! direct_const_arg { + ($($arg:tt)*) => { + /* compiler built-in */ + }; +} + /// A marker for types which can be used as types of `const` generic parameters. /// /// These types must have a proper equivalence relation (`Eq`) and it must be automatically diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs index 7400891f75b58..7b47141c9fb8a 100644 --- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs +++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs @@ -537,6 +537,7 @@ fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { | TyKind::Pat(..) | TyKind::FieldOf(..) | TyKind::View(..) + | TyKind::DirectConstArg(..) // unused | TyKind::CVarArgs diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index f194103ae2e88..cb0db1ed014fc 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -248,6 +248,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Array(..) | ast::ExprKind::While(..) | ast::ExprKind::Await(..) + | ast::ExprKind::DirectConstArg(..) | ast::ExprKind::Err(_) | ast::ExprKind::Dummy | ast::ExprKind::UnsafeBinderCast(..) => Sugg::NonParen(snippet(expr.span)), diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 8a3674bff1ca6..cc7fdaefc8fee 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -486,7 +486,8 @@ pub(crate) fn format_expr( | ast::ExprKind::Type(..) | ast::ExprKind::IncludedBytes(..) | ast::ExprKind::OffsetOf(..) - | ast::ExprKind::UnsafeBinderCast(..) => { + | ast::ExprKind::UnsafeBinderCast(..) + | ast::ExprKind::DirectConstArg(..) => { // These don't normally occur in the AST because macros aren't expanded. However, // rustfmt tries to parse macro arguments when formatting macros, so it's not totally // impossible for rustfmt to come across one of these nodes when formatting a file. diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs index 9e7bbcc243ccf..d3a1279cfb325 100644 --- a/src/tools/rustfmt/src/types.rs +++ b/src/tools/rustfmt/src/types.rs @@ -1014,7 +1014,6 @@ impl Rewrite for ast::Ty { }) } ast::TyKind::CVarArgs => Ok("...".to_owned()), - ast::TyKind::Dummy | ast::TyKind::Err(_) => Ok(context.snippet(self.span).to_owned()), ast::TyKind::FieldOf(ref ty, ref variant, ref field) => { let ty = ty.rewrite_result(context, shape)?; if let Some(variant) = variant { @@ -1049,14 +1048,14 @@ impl Rewrite for ast::Ty { result.push_str(&rewrite); Ok(result) } - - ast::TyKind::Pat(..) | ast::TyKind::View(..) => { + ast::TyKind::Pat(..) | ast::TyKind::View(..) | ast::TyKind::DirectConstArg(..) => { // These don't normally occur in the AST because macros aren't expanded. However, // rustfmt tries to parse macro arguments when formatting macros, so it's not // totally impossible for rustfmt to come across these nodes when formatting a file. // Also, rustfmt might get passed the output from `-Zunpretty=expanded`. Err(RewriteError::Unknown) } + ast::TyKind::Dummy | ast::TyKind::Err(_) => Ok(context.snippet(self.span).to_owned()), } } } diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index 1fcc59f2a1ea4..3e06f3899d1b3 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -532,9 +532,8 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr | ast::ExprKind::Index(_, ref expr, _) | ast::ExprKind::Unary(_, ref expr) | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Yield(YieldKind::Prefix(Some(ref expr))) => { - is_block_expr(context, expr, repr) - } + | ast::ExprKind::Yield(YieldKind::Prefix(Some(ref expr))) + | ast::ExprKind::DirectConstArg(ref expr) => is_block_expr(context, expr, repr), ast::ExprKind::Closure(ref closure) => is_block_expr(context, &closure.body, repr), // This can only be a string lit ast::ExprKind::Lit(_) => { diff --git a/src/tools/rustfmt/tests/source/direct_const_arg.rs b/src/tools/rustfmt/tests/source/direct_const_arg.rs new file mode 100644 index 0000000000000..f6f9c25d9a0d8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/direct_const_arg.rs @@ -0,0 +1,14 @@ +// direct_const_arg! is a built-in macro relevant to min_generic_const_args; its contents should be +// formatted as if its contents were passed through unchanged (the macro changes the semantics of +// the contained expression, the syntax is unchanged) + +#![feature(min_generic_const_args)] + +trait Trait { + type const TYPE_CONST: usize; +} + +struct S; + +fn parsed_as_expr_kind(_: S<{ core::direct_const_arg!( T :: TYPE_CONST ) }>) {} +fn parsed_as_ty_kind(_: S< core::direct_const_arg!( T :: TYPE_CONST ) >) {} diff --git a/src/tools/rustfmt/tests/target/direct_const_arg.rs b/src/tools/rustfmt/tests/target/direct_const_arg.rs new file mode 100644 index 0000000000000..1ebadae00ff05 --- /dev/null +++ b/src/tools/rustfmt/tests/target/direct_const_arg.rs @@ -0,0 +1,14 @@ +// direct_const_arg! is a built-in macro relevant to min_generic_const_args; its contents should be +// formatted as if its contents were passed through unchanged (the macro changes the semantics of +// the contained expression, the syntax is unchanged) + +#![feature(min_generic_const_args)] + +trait Trait { + type const TYPE_CONST: usize; +} + +struct S; + +fn parsed_as_expr_kind(_: S<{ core::direct_const_arg!(T::TYPE_CONST) }>) {} +fn parsed_as_ty_kind(_: S) {} diff --git a/tests/pretty/direct-const-arg.pp b/tests/pretty/direct-const-arg.pp new file mode 100644 index 0000000000000..a76ed9a480e71 --- /dev/null +++ b/tests/pretty/direct-const-arg.pp @@ -0,0 +1,15 @@ +#![feature(prelude_import)] +#![no_std] +//@ pretty-mode:expanded +//@ pp-exact:direct-const-arg.pp +#![feature(min_generic_const_args)] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; + +fn f() {} + +fn main() { + f::(); + f::<{ core::direct_const_arg! (2) }>(); +} diff --git a/tests/pretty/direct-const-arg.rs b/tests/pretty/direct-const-arg.rs new file mode 100644 index 0000000000000..330c1cac024de --- /dev/null +++ b/tests/pretty/direct-const-arg.rs @@ -0,0 +1,10 @@ +//@ pretty-mode:expanded +//@ pp-exact:direct-const-arg.pp +#![feature(min_generic_const_args)] + +fn f() {} + +fn main() { + f::(); + f::<{ core::direct_const_arg!(2) }>(); +} diff --git a/tests/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr b/tests/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr index 62bebd53b14a6..6cf4e881adae8 100644 --- a/tests/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr +++ b/tests/ui/const-generics/generic_const_exprs/assoc_const_unification/doesnt_unify_evaluatable.stderr @@ -1,8 +1,8 @@ error: unconstrained generic constant - --> $DIR/doesnt_unify_evaluatable.rs:9:13 + --> $DIR/doesnt_unify_evaluatable.rs:9:11 | LL | bar::<{ T::ASSOC }>(); - | ^^^^^^^^ + | ^^^^^^^^^^^^ | help: try adding a `where` bound | diff --git a/tests/ui/const-generics/mgca/adt_expr_arg_simple.rs b/tests/ui/const-generics/mgca/adt_expr_arg_simple.rs index 8470b933cadd2..b09d17fb11262 100644 --- a/tests/ui/const-generics/mgca/adt_expr_arg_simple.rs +++ b/tests/ui/const-generics/mgca/adt_expr_arg_simple.rs @@ -10,7 +10,6 @@ use Option::Some; fn foo>() {} - trait Trait { type const ASSOC: u32; } @@ -26,7 +25,7 @@ fn bar() { // this on the other hand is not allowed as `N + 1` is not a legal // const argument - foo::<{ Some:: { 0: N + 1 } }>(); + foo::<{ core::direct_const_arg!(Some:: { 0: N + 1 }) }>(); //~^ ERROR: complex const arguments must be placed inside of a `const` block // this also is not allowed as generic parameters cannot be used diff --git a/tests/ui/const-generics/mgca/adt_expr_arg_simple.stderr b/tests/ui/const-generics/mgca/adt_expr_arg_simple.stderr index 0060c94875b5c..8f02ce0f82315 100644 --- a/tests/ui/const-generics/mgca/adt_expr_arg_simple.stderr +++ b/tests/ui/const-generics/mgca/adt_expr_arg_simple.stderr @@ -1,11 +1,11 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/adt_expr_arg_simple.rs:29:30 + --> $DIR/adt_expr_arg_simple.rs:28:54 | -LL | foo::<{ Some:: { 0: N + 1 } }>(); - | ^^^^^ +LL | foo::<{ core::direct_const_arg!(Some:: { 0: N + 1 }) }>(); + | ^^^^^ error: generic parameters may not be used in const operations - --> $DIR/adt_expr_arg_simple.rs:34:38 + --> $DIR/adt_expr_arg_simple.rs:33:38 | LL | foo::<{ Some:: { 0: const { N + 1 } } }>(); | ^ diff --git a/tests/ui/const-generics/mgca/array-expr-complex.r1.stderr b/tests/ui/const-generics/mgca/array-expr-complex.r1.stderr index 0c931af8d8b1c..a226d7ff0c225 100644 --- a/tests/ui/const-generics/mgca/array-expr-complex.r1.stderr +++ b/tests/ui/const-generics/mgca/array-expr-complex.r1.stderr @@ -1,8 +1,8 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/array-expr-complex.rs:11:28 + --> $DIR/array-expr-complex.rs:11:52 | -LL | takes_array::<{ [1, 2, 1 + 2] }>(); - | ^^^^^ +LL | takes_array::<{ core::direct_const_arg!([1, 2, 1 + 2]) }>(); + | ^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/mgca/array-expr-complex.r2.stderr b/tests/ui/const-generics/mgca/array-expr-complex.r2.stderr index 335af9235e0c9..cc1e70c1d9a7a 100644 --- a/tests/ui/const-generics/mgca/array-expr-complex.r2.stderr +++ b/tests/ui/const-generics/mgca/array-expr-complex.r2.stderr @@ -1,8 +1,8 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/array-expr-complex.rs:14:21 + --> $DIR/array-expr-complex.rs:14:45 | -LL | takes_array::<{ [X; 3] }>(); - | ^^^^^^ +LL | takes_array::<{ core::direct_const_arg!([X; 3]) }>(); + | ^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/mgca/array-expr-complex.r3.stderr b/tests/ui/const-generics/mgca/array-expr-complex.r3.stderr index 02d74c1b5d0c5..cc52abe0e8fb8 100644 --- a/tests/ui/const-generics/mgca/array-expr-complex.r3.stderr +++ b/tests/ui/const-generics/mgca/array-expr-complex.r3.stderr @@ -1,8 +1,8 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/array-expr-complex.rs:17:21 + --> $DIR/array-expr-complex.rs:17:45 | -LL | takes_array::<{ [0; Y] }>(); - | ^^^^^^ +LL | takes_array::<{ core::direct_const_arg!([0; Y]) }>(); + | ^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/mgca/array-expr-complex.rs b/tests/ui/const-generics/mgca/array-expr-complex.rs index 26f4700b5885c..7f5a77fdff3df 100644 --- a/tests/ui/const-generics/mgca/array-expr-complex.rs +++ b/tests/ui/const-generics/mgca/array-expr-complex.rs @@ -8,13 +8,13 @@ fn takes_array() {} fn generic_caller() { // not supported yet #[cfg(r1)] - takes_array::<{ [1, 2, 1 + 2] }>(); + takes_array::<{ core::direct_const_arg!([1, 2, 1 + 2]) }>(); //[r1]~^ ERROR: complex const arguments must be placed inside of a `const` block #[cfg(r2)] - takes_array::<{ [X; 3] }>(); + takes_array::<{ core::direct_const_arg!([X; 3]) }>(); //[r2]~^ ERROR: complex const arguments must be placed inside of a `const` block #[cfg(r3)] - takes_array::<{ [0; Y] }>(); + takes_array::<{ core::direct_const_arg!([0; Y]) }>(); //[r3]~^ ERROR: complex const arguments must be placed inside of a `const` block } diff --git a/tests/ui/const-generics/mgca/array_expr_arg_complex.rs b/tests/ui/const-generics/mgca/array_expr_arg_complex.rs index 6d57e4f4b9686..4adb2bf6e429d 100644 --- a/tests/ui/const-generics/mgca/array_expr_arg_complex.rs +++ b/tests/ui/const-generics/mgca/array_expr_arg_complex.rs @@ -9,8 +9,8 @@ fn takes_array() {} fn takes_tuple_with_array() {} fn generic_caller() { - takes_array::<{ [N, N + 1] }>(); //~ ERROR complex const arguments must be placed inside of a `const` block - takes_tuple_with_array::<{ ([N, N + 1], N) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block + takes_array::<{ core::direct_const_arg!([N, N + 1]) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block + takes_tuple_with_array::<{ core::direct_const_arg!(([N, N + 1], N)) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block } fn main() {} diff --git a/tests/ui/const-generics/mgca/array_expr_arg_complex.stderr b/tests/ui/const-generics/mgca/array_expr_arg_complex.stderr index f40d4b5035d86..c7b13351d453c 100644 --- a/tests/ui/const-generics/mgca/array_expr_arg_complex.stderr +++ b/tests/ui/const-generics/mgca/array_expr_arg_complex.stderr @@ -1,14 +1,14 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/array_expr_arg_complex.rs:12:25 + --> $DIR/array_expr_arg_complex.rs:12:49 | -LL | takes_array::<{ [N, N + 1] }>(); - | ^^^^^ +LL | takes_array::<{ core::direct_const_arg!([N, N + 1]) }>(); + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/array_expr_arg_complex.rs:13:37 + --> $DIR/array_expr_arg_complex.rs:13:61 | -LL | takes_tuple_with_array::<{ ([N, N + 1], N) }>(); - | ^^^^^ +LL | takes_tuple_with_array::<{ core::direct_const_arg!(([N, N + 1], N)) }>(); + | ^^^^^ error: aborting due to 2 previous errors diff --git a/tests/ui/const-generics/mgca/auxiliary/anon-const-def-id-on-const-arg-with-anon.rs b/tests/ui/const-generics/mgca/auxiliary/anon-const-def-id-on-const-arg-with-anon.rs new file mode 100644 index 0000000000000..c1ebfc18e0816 --- /dev/null +++ b/tests/ui/const-generics/mgca/auxiliary/anon-const-def-id-on-const-arg-with-anon.rs @@ -0,0 +1,11 @@ +#![feature(min_generic_const_args)] +#![allow(incomplete_features)] +pub struct S; +// this is a directly represented anon const... it's a bit weird. +// imagine `S<{ (2, const { 1 + 1 }) }>`. a directly represented tuple, containing an anon const. +// now, replace `(2, _)` with `_`. it's a directly represented anon const. +// this is different from const argument lowering failing to represent an argument directly and +// falling back to representing it as an anon const instead. +pub fn f() -> S<{ const { 1 + 1 } }> { + S +} diff --git a/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.rs b/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.rs index 7c7ffd9a9bd5f..e3d1f577d4f60 100644 --- a/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.rs +++ b/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.rs @@ -2,10 +2,11 @@ trait Iter< const FN: fn() = { - || { //~ ERROR complex const arguments must be placed inside of a `const` block + core::direct_const_arg!(|| { + //~^ ERROR complex const arguments must be placed inside of a `const` block use std::io::*; write!(_, "") - } + }) }, > { diff --git a/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.stderr b/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.stderr index 60774c4a3efea..96fcea9e906cf 100644 --- a/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.stderr +++ b/tests/ui/const-generics/mgca/bad-const-arg-fn-154539.stderr @@ -1,10 +1,12 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/bad-const-arg-fn-154539.rs:5:9 + --> $DIR/bad-const-arg-fn-154539.rs:5:33 | -LL | / || { +LL | core::direct_const_arg!(|| { + | _________________________________^ +LL | | LL | | use std::io::*; LL | | write!(_, "") -LL | | } +LL | | }) | |_________^ error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/mgca/bad-direct-const-arg.rs b/tests/ui/const-generics/mgca/bad-direct-const-arg.rs new file mode 100644 index 0000000000000..451806ca6e1db --- /dev/null +++ b/tests/ui/const-generics/mgca/bad-direct-const-arg.rs @@ -0,0 +1,8 @@ +//! Simple error message test, nothing special here +#![feature(min_generic_const_args)] + +fn main(x: core::direct_const_arg!(2)) { + //~^ ERROR expected type, found `direct_const_arg!()` constant + let _ = core::direct_const_arg!(2); + //~^ ERROR expected expression, found `direct_const_arg!()` constant +} diff --git a/tests/ui/const-generics/mgca/bad-direct-const-arg.stderr b/tests/ui/const-generics/mgca/bad-direct-const-arg.stderr new file mode 100644 index 0000000000000..b77791ad5b3ee --- /dev/null +++ b/tests/ui/const-generics/mgca/bad-direct-const-arg.stderr @@ -0,0 +1,14 @@ +error: expected expression, found `direct_const_arg!()` constant + --> $DIR/bad-direct-const-arg.rs:6:13 + | +LL | let _ = core::direct_const_arg!(2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected type, found `direct_const_arg!()` constant + --> $DIR/bad-direct-const-arg.rs:4:12 + | +LL | fn main(x: core::direct_const_arg!(2)) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/const-generics/mgca/direct-const-arg-feature-gate.rs b/tests/ui/const-generics/mgca/direct-const-arg-feature-gate.rs new file mode 100644 index 0000000000000..16ba349a29be6 --- /dev/null +++ b/tests/ui/const-generics/mgca/direct-const-arg-feature-gate.rs @@ -0,0 +1,5 @@ +fn foo(_: [(); core::direct_const_arg!(N)]) {} +//~^ ERROR use of unstable library feature `min_generic_const_args` +//~| ERROR expected expression, found `direct_const_arg!()` constant +//~| ERROR generic parameters may not be used in const operations +fn main() {} diff --git a/tests/ui/const-generics/mgca/direct-const-arg-feature-gate.stderr b/tests/ui/const-generics/mgca/direct-const-arg-feature-gate.stderr new file mode 100644 index 0000000000000..f5dc211c2f71a --- /dev/null +++ b/tests/ui/const-generics/mgca/direct-const-arg-feature-gate.stderr @@ -0,0 +1,28 @@ +error[E0658]: use of unstable library feature `min_generic_const_args` + --> $DIR/direct-const-arg-feature-gate.rs:1:32 + | +LL | fn foo(_: [(); core::direct_const_arg!(N)]) {} + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #132980 for more information + = help: add `#![feature(min_generic_const_args)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: generic parameters may not be used in const operations + --> $DIR/direct-const-arg-feature-gate.rs:1:56 + | +LL | fn foo(_: [(); core::direct_const_arg!(N)]) {} + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + +error: expected expression, found `direct_const_arg!()` constant + --> $DIR/direct-const-arg-feature-gate.rs:1:32 + | +LL | fn foo(_: [(); core::direct_const_arg!(N)]) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.rs b/tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.rs new file mode 100644 index 0000000000000..8c802e563db7c --- /dev/null +++ b/tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.rs @@ -0,0 +1,5 @@ +#![feature(min_generic_const_args)] +struct S; +fn foo(_: S) {} +//~^ ERROR direct_const_arg! takes 1 argument +fn main() {} diff --git a/tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.stderr b/tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.stderr new file mode 100644 index 0000000000000..6f54a563ffbfc --- /dev/null +++ b/tests/ui/const-generics/mgca/direct-const-arg-multiple-exprs.stderr @@ -0,0 +1,8 @@ +error: direct_const_arg! takes 1 argument + --> $DIR/direct-const-arg-multiple-exprs.rs:3:45 + | +LL | fn foo(_: S) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.rs b/tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.rs new file mode 100644 index 0000000000000..09bce5c7f9aa2 --- /dev/null +++ b/tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.rs @@ -0,0 +1,15 @@ +//! When doing some refactorings in mGCA, it was easy to accidentally stabilize doubly-brace-wrapped +//! paths. Check to make sure we don't accidentally do so. +//! +//! Feel free to delete this test if/when mGCA is stabilized and we support this syntax on stable, +//! it's testing nothing useful beyond that point. + +fn f() {} + +fn g() { + f::<{ N }>(); // ok + f::<{ { N } }>(); + //~^ ERROR: generic parameters may not be used in const operations +} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.stderr b/tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.stderr new file mode 100644 index 0000000000000..6168ec242a38c --- /dev/null +++ b/tests/ui/const-generics/mgca/double-wrapped-path-not-accidentally-stabilized.stderr @@ -0,0 +1,11 @@ +error: generic parameters may not be used in const operations + --> $DIR/double-wrapped-path-not-accidentally-stabilized.rs:11:13 + | +LL | f::<{ { N } }>(); + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + +error: aborting due to 1 previous error + diff --git a/tests/ui/const-generics/mgca/explicit_anon_consts.rs b/tests/ui/const-generics/mgca/explicit_anon_consts.rs index 2b9909b43dfbb..9b642b286fa3e 100644 --- a/tests/ui/const-generics/mgca/explicit_anon_consts.rs +++ b/tests/ui/const-generics/mgca/explicit_anon_consts.rs @@ -10,7 +10,7 @@ type Adt1 = Foo; type Adt2 = Foo<{ N }>; type Adt3 = Foo; //~^ ERROR: generic parameters may not be used in const operations -type Adt4 = Foo<{ 1 + 1 }>; +type Adt4 = Foo; //~^ ERROR: complex const arguments must be placed inside of a `const` block type Adt5 = Foo; @@ -18,7 +18,7 @@ type Arr = [(); N]; type Arr2 = [(); { N }]; type Arr3 = [(); const { N }]; //~^ ERROR: generic parameters may not be used in const operations -type Arr4 = [(); 1 + 1]; +type Arr4 = [(); core::direct_const_arg!(1 + 1)]; //~^ ERROR: complex const arguments must be placed inside of a `const` block type Arr5 = [(); const { 1 + 1 }]; @@ -27,7 +27,7 @@ fn repeats() -> [(); N] { let _2 = [(); { N }]; let _3 = [(); const { N }]; //~^ ERROR: generic parameters may not be used in const operations - let _4 = [(); 1 + 1]; + let _4 = [(); core::direct_const_arg!(1 + 1)]; //~^ ERROR: complex const arguments must be placed inside of a `const` block let _5 = [(); const { 1 + 1 }]; let _6: [(); const { N }] = todo!(); @@ -42,10 +42,10 @@ type const ITEM2: usize = { N }; type const ITEM3: usize = const { N }; //~^ ERROR: generic parameters may not be used in const operations -type const ITEM4: usize = { 1 + 1 }; +type const ITEM4: usize = core::direct_const_arg!(1 + 1); //~^ ERROR: complex const arguments must be placed inside of a `const` block -type const ITEM5: usize = const { 1 + 1}; +type const ITEM5: usize = const { 1 + 1 }; trait Trait { @@ -59,7 +59,7 @@ fn ace_bounds< T2: Trait, T3: Trait, //~^ ERROR: generic parameters may not be used in const operations - T4: Trait, + T4: Trait, //~^ ERROR: complex const arguments must be placed inside of a `const` block T5: Trait, >() {} @@ -68,6 +68,6 @@ struct Default1; struct Default2; struct Default3; //~^ ERROR: generic parameters may not be used in const operations -struct Default4; +struct Default4; //~^ ERROR: complex const arguments must be placed inside of a `const` block -struct Default5; +struct Default5; diff --git a/tests/ui/const-generics/mgca/explicit_anon_consts.stderr b/tests/ui/const-generics/mgca/explicit_anon_consts.stderr index f634ec1cf12e4..9ab6af010bf21 100644 --- a/tests/ui/const-generics/mgca/explicit_anon_consts.stderr +++ b/tests/ui/const-generics/mgca/explicit_anon_consts.stderr @@ -1,38 +1,38 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/explicit_anon_consts.rs:13:35 + --> $DIR/explicit_anon_consts.rs:13:57 | -LL | type Adt4 = Foo<{ 1 + 1 }>; - | ^^^^^ +LL | type Adt4 = Foo; + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/explicit_anon_consts.rs:21:34 + --> $DIR/explicit_anon_consts.rs:21:58 | -LL | type Arr4 = [(); 1 + 1]; - | ^^^^^ +LL | type Arr4 = [(); core::direct_const_arg!(1 + 1)]; + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/explicit_anon_consts.rs:30:19 + --> $DIR/explicit_anon_consts.rs:30:43 | -LL | let _4 = [(); 1 + 1]; - | ^^^^^ +LL | let _4 = [(); core::direct_const_arg!(1 + 1)]; + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/explicit_anon_consts.rs:45:45 + --> $DIR/explicit_anon_consts.rs:45:67 | -LL | type const ITEM4: usize = { 1 + 1 }; - | ^^^^^ +LL | type const ITEM4: usize = core::direct_const_arg!(1 + 1); + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/explicit_anon_consts.rs:62:25 + --> $DIR/explicit_anon_consts.rs:62:49 | -LL | T4: Trait, - | ^^^^^ +LL | T4: Trait, + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/explicit_anon_consts.rs:71:52 + --> $DIR/explicit_anon_consts.rs:71:76 | -LL | struct Default4; - | ^^^^^ +LL | struct Default4; + | ^^^^^ error: generic parameters may not be used in const operations --> $DIR/explicit_anon_consts.rs:42:51 diff --git a/tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.rs b/tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.rs new file mode 100644 index 0000000000000..1653fd63ed3f8 --- /dev/null +++ b/tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.rs @@ -0,0 +1,17 @@ +//! Diagnostics for expressions that contain things that must be a mGCA direct expression, *and* +//! things that must be an anon const, are currently less than ideal. This test merely asserts the +//! current (bad) state of diagnostics, so we can track improvements over time. + +#![feature(min_generic_const_args, min_adt_const_params)] +#![allow(incomplete_features)] + +fn f() {} + +fn g() { + f::<{ (N, 1 + 1) }>(); + //~^ ERROR: generic parameters may not be used in const operations + f::<{ core::direct_const_arg!((N, 1 + 1)) }>(); + //~^ ERROR: complex const arguments must be placed inside of a `const` block +} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.stderr b/tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.stderr new file mode 100644 index 0000000000000..ae297de108493 --- /dev/null +++ b/tests/ui/const-generics/mgca/mixed-direct-anon-expression-diagnostics.stderr @@ -0,0 +1,16 @@ +error: complex const arguments must be placed inside of a `const` block + --> $DIR/mixed-direct-anon-expression-diagnostics.rs:13:39 + | +LL | f::<{ core::direct_const_arg!((N, 1 + 1)) }>(); + | ^^^^^ + +error: generic parameters may not be used in const operations + --> $DIR/mixed-direct-anon-expression-diagnostics.rs:11:12 + | +LL | f::<{ (N, 1 + 1) }>(); + | ^ + | + = help: add `#![feature(generic_const_args)]` to allow generic expressions as the RHS of const items + +error: aborting due to 2 previous errors + diff --git a/tests/ui/const-generics/mgca/serialized-direct-const-with-anon-const-body.rs b/tests/ui/const-generics/mgca/serialized-direct-const-with-anon-const-body.rs new file mode 100644 index 0000000000000..985e14fddb676 --- /dev/null +++ b/tests/ui/const-generics/mgca/serialized-direct-const-with-anon-const-body.rs @@ -0,0 +1,16 @@ +//@ check-pass +//@ aux-build:anon-const-def-id-on-const-arg-with-anon.rs +//! The DefCollector sometimes generates "fake" DefKind::AnonConst DefIds for AST AnonConsts that +//! are not actually lowered to HIR AnonConsts, and so the DefId is placed on a hir::Node::ConstArg +//! (just to place it *somewhere*). These "fake" DefIds should not be serialized. Previously, the +//! logic to skip serializing them was incorrect (we were still serializing fake DefIds for +//! `ConstArg(ConstArgKind::Anon)` if the directly represented expression contained within it +//! *another*, unrelated, anon const). This test checks that case, a directly-represented +//! fake-anon-const directly containing another anon const. + +#![feature(min_generic_const_args)] +#![allow(incomplete_features)] +extern crate anon_const_def_id_on_const_arg_with_anon; +fn main() { + anon_const_def_id_on_const_arg_with_anon::f(); +} diff --git a/tests/ui/const-generics/mgca/tuple_ctor_complex_args.rs b/tests/ui/const-generics/mgca/tuple_ctor_complex_args.rs index 2e39f8952b11d..460c8af840a1c 100644 --- a/tests/ui/const-generics/mgca/tuple_ctor_complex_args.rs +++ b/tests/ui/const-generics/mgca/tuple_ctor_complex_args.rs @@ -9,7 +9,7 @@ struct Point(u32, u32); fn with_point() {} fn test() { - with_point::<{ Point(N + 1, N) }>(); + with_point::<{ core::direct_const_arg!(Point(N + 1, N)) }>(); //~^ ERROR complex const arguments must be placed inside of a `const` block with_point::<{ Point(const { N + 1 }, N) }>(); diff --git a/tests/ui/const-generics/mgca/tuple_ctor_complex_args.stderr b/tests/ui/const-generics/mgca/tuple_ctor_complex_args.stderr index 3a873ec33fb19..a4e7cb94c57c3 100644 --- a/tests/ui/const-generics/mgca/tuple_ctor_complex_args.stderr +++ b/tests/ui/const-generics/mgca/tuple_ctor_complex_args.stderr @@ -1,8 +1,8 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/tuple_ctor_complex_args.rs:12:26 + --> $DIR/tuple_ctor_complex_args.rs:12:50 | -LL | with_point::<{ Point(N + 1, N) }>(); - | ^^^^^ +LL | with_point::<{ core::direct_const_arg!(Point(N + 1, N)) }>(); + | ^^^^^ error: generic parameters may not be used in const operations --> $DIR/tuple_ctor_complex_args.rs:15:34 diff --git a/tests/ui/const-generics/mgca/tuple_expr_arg_complex.rs b/tests/ui/const-generics/mgca/tuple_expr_arg_complex.rs index d7cab17bad124..0d99b8ae345d6 100644 --- a/tests/ui/const-generics/mgca/tuple_expr_arg_complex.rs +++ b/tests/ui/const-generics/mgca/tuple_expr_arg_complex.rs @@ -9,11 +9,11 @@ fn takes_tuple() {} fn takes_nested_tuple() {} fn generic_caller() { - takes_tuple::<{ (N, N + 1) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block - takes_tuple::<{ (N, T::ASSOC + 1) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block + takes_tuple::<{ core::direct_const_arg!((N, N + 1)) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block + takes_tuple::<{ core::direct_const_arg!((N, T::ASSOC + 1)) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block - takes_nested_tuple::<{ (N, (N, N + 1)) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block - takes_nested_tuple::<{ (N, (N, const { N + 1 })) }>(); //~ ERROR generic parameters may not be used in const operations + takes_nested_tuple::<{ core::direct_const_arg!((N, (N, N + 1))) }>(); //~ ERROR complex const arguments must be placed inside of a `const` block + takes_nested_tuple::<{ core::direct_const_arg!((N, (N, const { N + 1 }))) }>(); //~ ERROR generic parameters may not be used in const operations } fn main() {} diff --git a/tests/ui/const-generics/mgca/tuple_expr_arg_complex.stderr b/tests/ui/const-generics/mgca/tuple_expr_arg_complex.stderr index a9d412964da29..4bed120284c08 100644 --- a/tests/ui/const-generics/mgca/tuple_expr_arg_complex.stderr +++ b/tests/ui/const-generics/mgca/tuple_expr_arg_complex.stderr @@ -1,26 +1,26 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/tuple_expr_arg_complex.rs:12:25 + --> $DIR/tuple_expr_arg_complex.rs:12:49 | -LL | takes_tuple::<{ (N, N + 1) }>(); - | ^^^^^ +LL | takes_tuple::<{ core::direct_const_arg!((N, N + 1)) }>(); + | ^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/tuple_expr_arg_complex.rs:13:25 + --> $DIR/tuple_expr_arg_complex.rs:13:49 | -LL | takes_tuple::<{ (N, T::ASSOC + 1) }>(); - | ^^^^^^^^^^^^ +LL | takes_tuple::<{ core::direct_const_arg!((N, T::ASSOC + 1)) }>(); + | ^^^^^^^^^^^^ error: complex const arguments must be placed inside of a `const` block - --> $DIR/tuple_expr_arg_complex.rs:15:36 + --> $DIR/tuple_expr_arg_complex.rs:15:60 | -LL | takes_nested_tuple::<{ (N, (N, N + 1)) }>(); - | ^^^^^ +LL | takes_nested_tuple::<{ core::direct_const_arg!((N, (N, N + 1))) }>(); + | ^^^^^ error: generic parameters may not be used in const operations - --> $DIR/tuple_expr_arg_complex.rs:16:44 + --> $DIR/tuple_expr_arg_complex.rs:16:68 | -LL | takes_nested_tuple::<{ (N, (N, const { N + 1 })) }>(); - | ^ +LL | takes_nested_tuple::<{ core::direct_const_arg!((N, (N, const { N + 1 }))) }>(); + | ^ | = help: add `#![feature(generic_const_args)]` to allow generic expressions as the RHS of const items diff --git a/tests/ui/const-generics/mgca/type-const-free-anon-const-mismatch.stderr b/tests/ui/const-generics/mgca/type-const-free-anon-const-mismatch.stderr index d0339d09cdc7a..52ef108a84dbf 100644 --- a/tests/ui/const-generics/mgca/type-const-free-anon-const-mismatch.stderr +++ b/tests/ui/const-generics/mgca/type-const-free-anon-const-mismatch.stderr @@ -4,7 +4,7 @@ error[E0284]: type annotations needed LL | type const X: usize = const { N }; | ^^^^^^^^^^^^^^^^^^^ cannot infer the value of the constant `_` | - = note: cannot satisfy `X::{constant#0} == _` + = note: cannot satisfy `X::{constant#0}::{constant#0} == _` error: the constant `"this isn't a usize"` is not of type `usize` --> $DIR/type-const-free-anon-const-mismatch.rs:8:1 diff --git a/tests/ui/const-generics/mgca/type-const-free-value-type-mismatch.next.stderr b/tests/ui/const-generics/mgca/type-const-free-value-type-mismatch.next.stderr index d96726829ee0b..3ef6ede90d933 100644 --- a/tests/ui/const-generics/mgca/type-const-free-value-type-mismatch.next.stderr +++ b/tests/ui/const-generics/mgca/type-const-free-value-type-mismatch.next.stderr @@ -10,7 +10,7 @@ error[E0284]: type annotations needed LL | fn f() -> [u8; const { N }] {} | ^^^^^^^^^^^^^^^^^ cannot infer the value of the constant `_` | - = note: cannot satisfy `f::{constant#0} == _` + = note: cannot satisfy `f::{constant#0}::{constant#0} == _` error[E0308]: mismatched types --> $DIR/type-const-free-value-type-mismatch.rs:11:11 diff --git a/tests/ui/const-generics/mgca/type-const-inherent-value-type-mismatch.next.stderr b/tests/ui/const-generics/mgca/type-const-inherent-value-type-mismatch.next.stderr index eeabde2d06320..551a1d496e910 100644 --- a/tests/ui/const-generics/mgca/type-const-inherent-value-type-mismatch.next.stderr +++ b/tests/ui/const-generics/mgca/type-const-inherent-value-type-mismatch.next.stderr @@ -4,7 +4,7 @@ error[E0284]: type annotations needed LL | fn f() -> [u8; const { Struct::N }] {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer the value of the constant `_` | - = note: cannot satisfy `f::{constant#0} == _` + = note: cannot satisfy `f::{constant#0}::{constant#0} == _` error: the constant `"this isn't a usize"` is not of type `usize` --> $DIR/type-const-inherent-value-type-mismatch.rs:13:5 diff --git a/tests/ui/const-generics/mgca/type-const-value-type-mismatch.next.stderr b/tests/ui/const-generics/mgca/type-const-value-type-mismatch.next.stderr index 123c442a3938d..748ffe1294e0a 100644 --- a/tests/ui/const-generics/mgca/type-const-value-type-mismatch.next.stderr +++ b/tests/ui/const-generics/mgca/type-const-value-type-mismatch.next.stderr @@ -16,7 +16,7 @@ error[E0284]: type annotations needed LL | fn arr() -> [u8; const { Self::LEN }] {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer the value of the constant `_` | - = note: cannot satisfy `::arr::{constant#0} == _` + = note: cannot satisfy `::arr::{constant#0}::{constant#0} == _` error[E0308]: mismatched types --> $DIR/type-const-value-type-mismatch.rs:21:17 diff --git a/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155125.stderr b/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155125.stderr index d2f0e87ecb593..6675831bddc86 100644 --- a/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155125.stderr +++ b/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155125.stderr @@ -9,13 +9,14 @@ LL | reuse foo; = note: `foo` must be defined only once in the value namespace of this block error: complex const arguments must be placed inside of a `const` block - --> $DIR/hir-crate-items-before-lowering-ices.rs:10:13 + --> $DIR/hir-crate-items-before-lowering-ices.rs:10:37 | -LL | / { +LL | core::direct_const_arg!({ + | _____________________________________^ LL | | fn foo() {} LL | | reuse foo; LL | | 2 -LL | | }, +LL | | }), | |_____________^ error: aborting due to 2 previous errors diff --git a/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155164.stderr b/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155164.stderr index 34d1a92ccd225..6164dabe74bff 100644 --- a/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155164.stderr +++ b/tests/ui/delegation/hir-crate-items-before-lowering-ices.ice_155164.stderr @@ -1,12 +1,13 @@ error: complex const arguments must be placed inside of a `const` block - --> $DIR/hir-crate-items-before-lowering-ices.rs:47:13 + --> $DIR/hir-crate-items-before-lowering-ices.rs:47:37 | -LL | / { +LL | core::direct_const_arg!({ + | _____________________________________^ LL | | LL | | struct W; LL | | impl W { ... | -LL | | }, +LL | | }), | |_____________^ error: aborting due to 1 previous error diff --git a/tests/ui/delegation/hir-crate-items-before-lowering-ices.rs b/tests/ui/delegation/hir-crate-items-before-lowering-ices.rs index b9a7a73732cf3..07f5a1ad1712f 100644 --- a/tests/ui/delegation/hir-crate-items-before-lowering-ices.rs +++ b/tests/ui/delegation/hir-crate-items-before-lowering-ices.rs @@ -7,11 +7,11 @@ mod ice_155125 { struct S; impl S< - { //[ice_155125]~ ERROR: complex const arguments must be placed inside of a `const` block + core::direct_const_arg!({ //[ice_155125]~ ERROR: complex const arguments must be placed inside of a `const` block fn foo() {} reuse foo; //[ice_155125]~ ERROR: the name `foo` is defined multiple times 2 - }, + }), > { } @@ -44,13 +44,13 @@ mod ice_155128 { mod ice_155164 { struct X { inner: std::iter::Map< - { + core::direct_const_arg!({ //[ice_155164]~^ ERROR: complex const arguments must be placed inside of a `const` block struct W; impl W { reuse Iterator::fold; } - }, + }), F, >, } diff --git a/tests/ui/delegation/inside-const-body-ice-155300.rs b/tests/ui/delegation/inside-const-body-ice-155300.rs index 06addf7e5412d..2d13007bc807b 100644 --- a/tests/ui/delegation/inside-const-body-ice-155300.rs +++ b/tests/ui/delegation/inside-const-body-ice-155300.rs @@ -5,12 +5,13 @@ pub struct S; impl S< - { //~ ERROR: complex const arguments must be placed inside of a `const` block + core::direct_const_arg!({ + //~^ ERROR: complex const arguments must be placed inside of a `const` block fn foo() {} reuse foo::<> as bar; reuse bar; //~^ ERROR: the name `bar` is defined multiple times - }, + }), > { } diff --git a/tests/ui/delegation/inside-const-body-ice-155300.stderr b/tests/ui/delegation/inside-const-body-ice-155300.stderr index 0d2020c997200..f0cb830109f8a 100644 --- a/tests/ui/delegation/inside-const-body-ice-155300.stderr +++ b/tests/ui/delegation/inside-const-body-ice-155300.stderr @@ -1,5 +1,5 @@ error[E0428]: the name `bar` is defined multiple times - --> $DIR/inside-const-body-ice-155300.rs:11:13 + --> $DIR/inside-const-body-ice-155300.rs:12:13 | LL | reuse foo::<> as bar; | --------------------- previous definition of the value `bar` here @@ -9,14 +9,15 @@ LL | reuse bar; = note: `bar` must be defined only once in the value namespace of this block error: complex const arguments must be placed inside of a `const` block - --> $DIR/inside-const-body-ice-155300.rs:8:9 + --> $DIR/inside-const-body-ice-155300.rs:8:33 | -LL | / { +LL | core::direct_const_arg!({ + | _________________________________^ +LL | | LL | | fn foo() {} LL | | reuse foo::<> as bar; -LL | | reuse bar; -LL | | -LL | | }, +... | +LL | | }), | |_________^ error: aborting due to 2 previous errors From 1818498e7caf304bc8bcb54c4af72627f1c224b4 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Mon, 6 Jul 2026 15:05:10 +0800 Subject: [PATCH 42/78] unify `MaxUniverse`s --- .../src/infer/relate/generalize.rs | 49 +----- .../rustc_next_trait_solver/src/normalize.rs | 75 +-------- .../eval_ctxt/solver_region_constraints.rs | 4 +- compiler/rustc_type_ir/src/lib.rs | 2 + .../rustc_type_ir/src/region_constraint.rs | 87 +--------- compiler/rustc_type_ir/src/universe.rs | 154 ++++++++++++++++++ 6 files changed, 168 insertions(+), 203 deletions(-) create mode 100644 compiler/rustc_type_ir/src/universe.rs diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index ee6e13250e709..12c577d3a50e2 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -5,10 +5,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::def_id::DefId; use rustc_middle::bug; use rustc_middle::ty::error::TypeError; -use rustc_middle::ty::{ - self, InferConst, Term, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, - TypeVisitor, -}; +use rustc_middle::ty::{self, InferConst, Term, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use tracing::{debug, instrument, warn}; @@ -362,45 +359,6 @@ impl<'tcx> InferCtxt<'tcx> { } } -/// Finds the max universe present -struct MaxUniverse { - max_universe: ty::UniverseIndex, -} - -impl MaxUniverse { - fn new() -> Self { - MaxUniverse { max_universe: ty::UniverseIndex::ROOT } - } - - fn max_universe(self) -> ty::UniverseIndex { - self.max_universe - } -} - -impl<'tcx> TypeVisitor> for MaxUniverse { - fn visit_ty(&mut self, t: Ty<'tcx>) { - if let ty::Placeholder(placeholder) = t.kind() { - self.max_universe = self.max_universe.max(placeholder.universe); - } - - t.super_visit_with(self) - } - - fn visit_const(&mut self, c: ty::Const<'tcx>) { - if let ty::ConstKind::Placeholder(placeholder) = c.kind() { - self.max_universe = self.max_universe.max(placeholder.universe); - } - - c.super_visit_with(self) - } - - fn visit_region(&mut self, r: ty::Region<'tcx>) { - if let ty::RePlaceholder(placeholder) = r.kind() { - self.max_universe = self.max_universe.max(placeholder.universe); - } - } -} - /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to @@ -511,10 +469,9 @@ impl<'tcx> Generalizer<'_, 'tcx> { if is_nested_alias { return Err(e); } else { - let mut visitor = MaxUniverse::new(); - alias.visit_with(&mut visitor); + let alias_max_universe = ty::max_universe_of_placeholders(self.infcx, alias); let infer_replacement_is_complete = - self.for_universe.can_name(visitor.max_universe()) + self.for_universe.can_name(alias_max_universe) && !alias.has_escaping_bound_vars(); if !infer_replacement_is_complete { warn!("may incompletely handle alias type: {alias:?}"); diff --git a/compiler/rustc_next_trait_solver/src/normalize.rs b/compiler/rustc_next_trait_solver/src/normalize.rs index 761b141202d98..959e18071bb57 100644 --- a/compiler/rustc_next_trait_solver/src/normalize.rs +++ b/compiler/rustc_next_trait_solver/src/normalize.rs @@ -3,9 +3,8 @@ use std::fmt::Debug; use rustc_type_ir::data_structures::ensure_sufficient_stack; use rustc_type_ir::inherent::*; use rustc_type_ir::{ - self as ty, AliasTerm, Binder, FallibleTypeFolder, InferConst, InferCtxtLike, InferTy, - Interner, TypeFoldable, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, - TypeVisitor, UniverseIndex, + self as ty, AliasTerm, Binder, FallibleTypeFolder, InferCtxtLike, Interner, TypeFoldable, + TypeSuperFoldable, TypeVisitableExt, UniverseIndex, }; use tracing::instrument; @@ -37,72 +36,6 @@ pub enum NormalizationWasAmbiguous { No, } -/// Finds the max universe present in infer vars. -struct MaxUniverse<'a, Infcx, I> -where - Infcx: InferCtxtLike, - I: Interner, -{ - infcx: &'a Infcx, - max_universe: ty::UniverseIndex, -} - -impl<'a, Infcx, I> MaxUniverse<'a, Infcx, I> -where - Infcx: InferCtxtLike, - I: Interner, -{ - fn new(infcx: &'a Infcx) -> Self { - MaxUniverse { infcx, max_universe: ty::UniverseIndex::ROOT } - } - - fn max_universe(self) -> ty::UniverseIndex { - self.max_universe - } -} - -impl<'a, Infcx, I> TypeVisitor for MaxUniverse<'a, Infcx, I> -where - Infcx: InferCtxtLike, - I: Interner, -{ - type Result = (); - - fn visit_ty(&mut self, t: I::Ty) { - if !t.has_infer() { - return; - } - - if let ty::Infer(InferTy::TyVar(vid)) = t.kind() { - // We shallow resolved the infer var before. - // So it should be a unresolved infer var with an universe. - self.max_universe = self.max_universe.max(self.infcx.universe_of_ty(vid).unwrap()); - } - - t.super_visit_with(self) - } - - fn visit_const(&mut self, c: I::Const) { - if !c.has_infer() { - return; - } - - if let ty::ConstKind::Infer(InferConst::Var(vid)) = c.kind() { - // We shallow resolved the infer var before. - // So it should be a unresolved infer var with an universe. - self.max_universe = self.max_universe.max(self.infcx.universe_of_ct(vid).unwrap()); - } - - c.super_visit_with(self) - } - - fn visit_region(&mut self, r: I::Region) { - if let ty::ReVar(vid) = r.kind() { - self.max_universe = self.max_universe.max(self.infcx.universe_of_lt(vid).unwrap()); - } - } -} - impl<'a, Infcx, I, F, E> NormalizationFolder<'a, Infcx, I, F> where Infcx: InferCtxtLike, @@ -130,9 +63,7 @@ where if normalization_was_ambiguous == NormalizationWasAmbiguous::Yes && has_escaping == HasEscapingBoundVars::Yes { - let mut visitor = MaxUniverse::new(self.infcx); - normalized.visit_with(&mut visitor); - let max_universe = visitor.max_universe(); + let max_universe = ty::max_universe_of_infer_vars(self.infcx, normalized); if max_universe.can_name(self.universes.first().unwrap().unwrap()) { return Ok(None); } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index aa92fc163f0ed..a232ab9192444 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -8,11 +8,11 @@ use rustc_type_ir::outlives::{Component, push_outlives_components}; #[cfg(not(feature = "nightly"))] use rustc_type_ir::region_constraint::TransitiveRelationBuilder; use rustc_type_ir::region_constraint::{ - Assumptions, RegionConstraint, eagerly_handle_placeholders_in_universe, max_universe, + Assumptions, RegionConstraint, eagerly_handle_placeholders_in_universe, }; use rustc_type_ir::{ AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesPredicate, TypeVisitable, - TypeVisitableExt, TypeVisitor, UniverseIndex, + TypeVisitableExt, TypeVisitor, UniverseIndex, max_universe, }; use tracing::{debug, instrument}; diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index 0fdfe17b963fa..bef53e422b0d2 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -56,6 +56,7 @@ mod term_kind; mod ty; mod ty_info; mod ty_kind; +mod universe; mod unnormalized; mod upcast; mod visit; @@ -86,6 +87,7 @@ pub use term_kind::*; pub use ty::{Alias, *}; pub use ty_info::*; pub use ty_kind::*; +pub use universe::*; pub use unnormalized::Unnormalized; pub use upcast::*; pub use visit::*; diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 626649027da28..e77e0b9e24d52 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -49,12 +49,11 @@ use crate::data_structures::IndexMap; use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; -use crate::visit::TypeSuperVisitable; use crate::{ - AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, ConstKind, DebruijnIndex, - FallibleTypeFolder, InferCtxtLike, InferTy, Interner, IsRigid, OutlivesPredicate, RegionKind, - TyKind, TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor, TypingMode, UniverseIndex, - Variance, VisitorResult, set_aliases_to_non_rigid, + AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, FallibleTypeFolder, + InferCtxtLike, Interner, IsRigid, OutlivesPredicate, RegionKind, TyKind, TypeFoldable, + TypeFolder, TypeVisitable, TypeVisitor, TypingMode, UniverseIndex, Variance, VisitorResult, + max_universe, set_aliases_to_non_rigid, }; #[derive_where(Clone, Debug; I: Interner)] @@ -990,84 +989,6 @@ pub fn regions_outlived_by_placeholder( }) } -/// The largest universe a variable or placeholder was from in `t` -pub fn max_universe, I: Interner, T: TypeFoldable>( - infcx: &Infcx, - t: T, -) -> UniverseIndex { - let mut visitor = MaxUniverse::new(infcx); - // `max_universe` is also used while rewriting constraints to lower universes, - // so do not rely on callers having already resolved non-region infer vars. - let t = infcx.resolve_vars_if_possible(t); - t.visit_with(&mut visitor); - visitor.max_universe() -} - -// FIXME(-Zassumptions-on-binders): Share this with the visitor used by generalization. We currently don't -// as generalization does not look at universes of inference variables but we do -struct MaxUniverse<'a, Infcx: InferCtxtLike> { - max_universe: UniverseIndex, - infcx: &'a Infcx, -} - -impl<'a, Infcx: InferCtxtLike> MaxUniverse<'a, Infcx> { - fn new(infcx: &'a Infcx) -> Self { - MaxUniverse { infcx, max_universe: UniverseIndex::ROOT } - } - - fn max_universe(self) -> UniverseIndex { - self.max_universe - } -} - -impl<'a, Infcx: InferCtxtLike, I: Interner> TypeVisitor - for MaxUniverse<'a, Infcx> -{ - type Result = (); - - fn visit_ty(&mut self, t: I::Ty) { - match t.kind() { - TyKind::Placeholder(p) => self.max_universe = self.max_universe.max(p.universe), - TyKind::Infer(InferTy::TyVar(inf)) => { - let u = self.infcx.universe_of_ty(inf).unwrap(); - debug!("var {inf:?} in universe {u:?}"); - self.max_universe = self.max_universe.max(u); - } - _ => t.super_visit_with(self), - } - } - - fn visit_const(&mut self, c: I::Const) { - match c.kind() { - ConstKind::Placeholder(p) => self.max_universe = self.max_universe.max(p.universe), - ConstKind::Infer(rustc_type_ir::InferConst::Var(inf)) => { - let u = self.infcx.universe_of_ct(inf).unwrap(); - debug!("var {inf:?} in universe {u:?}"); - self.max_universe = self.max_universe.max(u); - } - _ => c.super_visit_with(self), - } - } - - fn visit_region(&mut self, r: I::Region) { - match r.kind() { - RegionKind::RePlaceholder(p) => self.max_universe = self.max_universe.max(p.universe), - RegionKind::ReVar(var) => match self.infcx.opportunistic_resolve_lt_var(var).kind() { - RegionKind::RePlaceholder(p) => { - self.max_universe = self.max_universe.max(p.universe) - } - RegionKind::ReVar(var) => { - let u = self.infcx.universe_of_lt(var).unwrap(); - debug!("var {var:?} in universe {u:?}"); - self.max_universe = self.max_universe.max(u); - } - _ => (), - }, - _ => (), - } - } -} - pub struct PlaceholderReplacer { cx: I, existing_var_count: usize, diff --git a/compiler/rustc_type_ir/src/universe.rs b/compiler/rustc_type_ir/src/universe.rs new file mode 100644 index 0000000000000..9b0990d4d0ff0 --- /dev/null +++ b/compiler/rustc_type_ir/src/universe.rs @@ -0,0 +1,154 @@ +use tracing::{debug, instrument}; + +use crate::inherent::*; +use crate::visit::TypeVisitableExt; +use crate::{ + ConstKind, InferCtxtLike, InferTy, Interner, RegionKind, TyKind, TypeFoldable, + TypeSuperVisitable, TypeVisitable, TypeVisitor, UniverseIndex, +}; + +/// The largest universe a variable or placeholder was from in `t` +pub fn max_universe, I: Interner, T: TypeFoldable>( + infcx: &Infcx, + t: T, +) -> UniverseIndex { + max_universe_inner::<_, _, _, true, true>(infcx, t) +} + +/// The largest universe a variable was from in `t` +pub fn max_universe_of_infer_vars< + Infcx: InferCtxtLike, + I: Interner, + T: TypeFoldable, +>( + infcx: &Infcx, + t: T, +) -> UniverseIndex { + max_universe_inner::<_, _, _, false, true>(infcx, t) +} + +/// The largest universe a placeholder was from in `t` +pub fn max_universe_of_placeholders< + Infcx: InferCtxtLike, + I: Interner, + T: TypeFoldable, +>( + infcx: &Infcx, + t: T, +) -> UniverseIndex { + max_universe_inner::<_, _, _, true, false>(infcx, t) +} + +fn max_universe_inner< + Infcx: InferCtxtLike, + I: Interner, + T: TypeFoldable, + const VISIT_PLACEHOLDER: bool, + const VISIT_INFER: bool, +>( + infcx: &Infcx, + t: T, +) -> UniverseIndex { + if !MaxUniverse::::needs_visit(&t) { + return UniverseIndex::ROOT; + } + + let mut visitor = MaxUniverse::<_, VISIT_PLACEHOLDER, VISIT_INFER>::new(infcx); + // FIXME: make this a debug_assert and let callers resolve vars if there's + // perf win here. + let t = infcx.resolve_vars_if_possible(t); + t.visit_with(&mut visitor); + visitor.max_universe() +} + +struct MaxUniverse<'a, Infcx: InferCtxtLike, const VISIT_PLACEHOLDER: bool, const VISIT_INFER: bool> +{ + max_universe: UniverseIndex, + infcx: &'a Infcx, +} + +impl<'a, Infcx: InferCtxtLike, const VISIT_PLACEHOLDER: bool, const VISIT_INFER: bool> + MaxUniverse<'a, Infcx, VISIT_PLACEHOLDER, VISIT_INFER> +{ + fn new(infcx: &'a Infcx) -> Self { + MaxUniverse { infcx, max_universe: UniverseIndex::ROOT } + } + + fn max_universe(self) -> UniverseIndex { + self.max_universe + } + + #[instrument(ret, level = "debug")] + fn needs_visit, I: Interner>(t: &T) -> bool { + (VISIT_PLACEHOLDER && t.has_placeholders()) || (VISIT_INFER && t.has_infer()) + } +} + +impl< + 'a, + Infcx: InferCtxtLike, + I: Interner, + const VISIT_PLACEHOLDER: bool, + const VISIT_INFER: bool, +> TypeVisitor for MaxUniverse<'a, Infcx, VISIT_PLACEHOLDER, VISIT_INFER> +{ + type Result = (); + + fn visit_ty(&mut self, t: I::Ty) { + if !Self::needs_visit(&t) { + return; + } + + match t.kind() { + TyKind::Placeholder(p) if VISIT_PLACEHOLDER => { + self.max_universe = self.max_universe.max(p.universe) + } + TyKind::Infer(InferTy::TyVar(inf)) if VISIT_INFER => { + let u = self.infcx.universe_of_ty(inf).unwrap(); + debug!("var {inf:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u); + } + _ => t.super_visit_with(self), + } + } + + fn visit_const(&mut self, c: I::Const) { + if !Self::needs_visit(&c) { + return; + } + + match c.kind() { + ConstKind::Placeholder(p) if VISIT_PLACEHOLDER => { + self.max_universe = self.max_universe.max(p.universe) + } + ConstKind::Infer(rustc_type_ir::InferConst::Var(inf)) if VISIT_INFER => { + let u = self.infcx.universe_of_ct(inf).unwrap(); + debug!("var {inf:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u); + } + _ => c.super_visit_with(self), + } + } + + fn visit_region(&mut self, r: I::Region) { + match r.kind() { + RegionKind::RePlaceholder(p) if VISIT_PLACEHOLDER => { + self.max_universe = self.max_universe.max(p.universe) + } + RegionKind::ReVar(var) if VISIT_INFER => { + match self.infcx.opportunistic_resolve_lt_var(var).kind() { + RegionKind::RePlaceholder(p) if VISIT_PLACEHOLDER => { + self.max_universe = self.max_universe.max(p.universe) + } + RegionKind::ReVar(var) if VISIT_INFER => { + let u = self.infcx.universe_of_lt(var).unwrap(); + debug!("var {var:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u); + } + _ => (), + } + } + _ => (), + } + } +} From fc267a6d557edb0d1cbe76a2c12fc4e9b7d07133 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Sun, 21 Jun 2026 15:16:08 +0200 Subject: [PATCH 43/78] Improve generic parameters handling for #[diagnostic::on_const] This prints generics parameters when these are concrete Adt generics args. A lint is emitted we try to format a generic that is found anywhere. Moreover, generics parameters that are ty infer vars are no longer displayed "as-is", it is not clear and might be confusing, in this specific case the name of the generic parameter will be displayed instead. --- compiler/rustc_passes/src/check_attr.rs | 33 ++++++++++++++--- compiler/rustc_passes/src/diagnostics.rs | 7 ++++ .../traits/fulfillment_errors.rs | 20 +++++++++-- .../traits/on_unimplemented.rs | 11 ++++-- ...generic_parameters_handling.current.stderr | 11 ++++++ .../generic_parameters_handling.next.stderr | 11 ++++++ .../on_const/generic_parameters_handling.rs | 25 +++++++++++++ .../on_const/malformed_format_literals.rs | 25 +++++++++++++ .../on_const/malformed_format_literals.stderr | 36 +++++++++++++++++++ 9 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr create mode 100644 tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr create mode 100644 tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7ae6005e9bc98..863e4d88872c9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -233,8 +233,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::OnUnimplemented { directive } => { self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref()) } - AttributeKind::OnConst { span, .. } => { - self.check_diagnostic_on_const(*span, hir_id, target, item) + AttributeKind::OnConst { span, directive } => { + self.check_diagnostic_on_const(*span, hir_id, target, item, directive.as_deref()) } AttributeKind::OnMove { directive } => { self.check_diagnostic_on_move(hir_id, directive.as_deref()) @@ -545,10 +545,36 @@ impl<'tcx> CheckAttrVisitor<'tcx> { hir_id: HirId, target: Target, item: Option>, + directive: Option<&Directive>, ) { // We only check the non-constness here. A diagnostic for use // on not-trait impl items is issued during attribute parsing. if target == (Target::Impl { of_trait: true }) { + if let Some(directive) = directive + && let Node::Item(Item { kind: ItemKind::Impl(hir::Impl { generics, .. }), .. }) = + self.tcx.hir_node(hir_id) + { + directive.visit_params(&mut |argument_name, span| { + let has_generic = generics.params.iter().any(|p| { + if !matches!(p.kind, GenericParamKind::Lifetime { .. }) + && let ParamName::Plain(name) = p.name + && name.name == argument_name + { + true + } else { + false + } + }); + if !has_generic { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + span, + diagnostics::OnConstMalformedFormatLiterals { name: argument_name }, + ) + } + }); + } match item.unwrap() { ItemLike::Item(it) => match it.expect_impl().constness { Constness::Const { .. } => { @@ -566,9 +592,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ItemLike::ForeignItem => {} } } - // FIXME(#155570) Can we do something with generic args here? - // regardless, we don't check the validity of generic args here - // ...whose generics would that be, anyway? The traits' or the impls'? } /// Checks use of generic formatting parameters in `#[diagnostic::on_move]` diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 92562cc462b87..605d54ac0511a 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -1196,6 +1196,13 @@ pub(crate) struct OnMoveMalformedFormatLiterals { pub name: Symbol, } +#[derive(Diagnostic)] +#[diag("unknown parameter `{$name}`")] +#[help(r#"expect either a generic argument name or {"`{Self}`"} as format argument"#)] +pub(crate) struct OnConstMalformedFormatLiterals { + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag("unused target expression is specified for glob or list delegation")] pub(crate) struct GlobOrListDelegationUnusedTargetExpr { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 617ec6c4ac229..b38c933151eb2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -29,8 +29,8 @@ use rustc_middle::ty::print::{ PrintTraitRefExt as _, with_forced_trimmed_paths, }; use rustc_middle::ty::{ - self, GenericArgKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, - TypeVisitableExt, Unnormalized, Upcast, + self, GenericArgKind, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, + TypeSuperFoldable, TypeVisitableExt, Unnormalized, Upcast, }; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::CrateNum; @@ -916,11 +916,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { find_attr!(self.tcx, impl_did, OnConst {directive, ..} => directive.as_deref()) .flatten() { - let (_, format_args) = self.on_unimplemented_components( + let (_, mut format_args) = self.on_unimplemented_components( trait_ref, main_obligation, diag.long_ty_path(), + false, ); + if let ty::Adt(def, args) = trait_ref.self_ty().skip_binder().kind() { + for param in self.tcx.generics_of(def.did()).own_params.iter() { + match param.kind { + GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. } => { + format_args + .generic_args + .push((param.name, args[param.index as usize].to_string())); + } + _ => continue, + } + } + } let CustomDiagnostic { message, label, notes, parent_label: _ } = command.eval(None, &format_args); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 138c53d013ebc..f7e4ec6164c99 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -46,7 +46,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { return CustomDiagnostic::default(); } let (filter_options, format_args) = - self.on_unimplemented_components(trait_pred, obligation, long_ty_path); + self.on_unimplemented_components(trait_pred, obligation, long_ty_path, true); if let Some(command) = find_attr!(self.tcx, trait_pred.def_id(), OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { command.eval( Some(&filter_options), @@ -62,6 +62,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, obligation: &PredicateObligation<'tcx>, long_ty_path: &mut Option, + print_infer_ty_var: bool, ) -> (FilterOptions, FormatArgs) { let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args); let trait_pred = trait_pred.skip_binder(); @@ -244,7 +245,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type() { - self.tcx.short_string(ty, long_ty_path) + if print_infer_ty_var == false + && let ty::Infer(ty::TyVar(_)) = ty.kind() + { + format!("{}", param.name) + } else { + self.tcx.short_string(ty, long_ty_path) + } } else { trait_pred.trait_ref.args[param.index as usize].to_string() } diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr new file mode 100644 index 0000000000000..f9e33ff4fac58 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `X: const Y<_>` is not satisfied + --> $DIR/generic_parameters_handling.rs:22:6 + | +LL | .blah(); + | ^^^^ + | + = note: Self = X, Z = Z, A = u8, B = &str + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr new file mode 100644 index 0000000000000..f9e33ff4fac58 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `X: const Y<_>` is not satisfied + --> $DIR/generic_parameters_handling.rs:22:6 + | +LL | .blah(); + | ^^^^ + | + = note: Self = X, Z = Z, A = u8, B = &str + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs new file mode 100644 index 0000000000000..32a0caf96988a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs @@ -0,0 +1,25 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +#![crate_type = "lib"] +#![feature(diagnostic_on_const, const_trait_impl)] + +pub struct X { + field: (A, B), +} + +pub const trait Y { + fn blah(&self) {} +} + +#[diagnostic::on_const(note = "Self = {Self}, Z = {Z}, A = {A}, B = {B}")] +impl Y for X {} + +const _: () = { + X { + field: (42_u8, "hello"), + } + .blah(); + //~^ ERROR the trait bound `X: const Y<_>` is not satisfied [E0277] + //~| NOTE Self = X, Z = Z, A = u8, B = &str +}; diff --git a/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs new file mode 100644 index 0000000000000..0c36ce25febb5 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs @@ -0,0 +1,25 @@ +#![crate_type = "lib"] +#![feature(diagnostic_on_const)] +#![feature(const_trait_impl)] + +pub struct X; + +const trait Y { + fn blah(&self) {} +} + +#[diagnostic::on_const( + message = "my message {Foo}", + //~^ WARN unknown parameter `Foo` + label = "my label {Bar}", + //~^ WARN unknown parameter `Bar` + note = "my label {Baz}", + //~^ WARN unknown parameter `Baz` +)] +impl Y for X {} + +const _: () = { + X {}.blah(); + //~^ ERROR my message {Foo} [E0277] + +}; diff --git a/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr new file mode 100644 index 0000000000000..48bdc2c239a85 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr @@ -0,0 +1,36 @@ +warning: unknown parameter `Foo` + --> $DIR/malformed_format_literals.rs:12:28 + | +LL | message = "my message {Foo}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: unknown parameter `Bar` + --> $DIR/malformed_format_literals.rs:14:24 + | +LL | label = "my label {Bar}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + +warning: unknown parameter `Baz` + --> $DIR/malformed_format_literals.rs:16:23 + | +LL | note = "my label {Baz}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + +error[E0277]: my message {Foo} + --> $DIR/malformed_format_literals.rs:22:10 + | +LL | X {}.blah(); + | ^^^^ my label {Bar} + | + = note: my label {Baz} + +error: aborting due to 1 previous error; 3 warnings emitted + +For more information about this error, try `rustc --explain E0277`. From abc324c79f9d9f2fb9eff05e3c7257f7020b7857 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 21 May 2026 15:40:10 +1000 Subject: [PATCH 44/78] Move `std::io::default_write_vectored` to `core::io` --- library/alloc/src/io/mod.rs | 4 +++- library/core/src/io/mod.rs | 2 ++ library/core/src/io/write.rs | 11 +++++++++++ library/std/src/io/mod.rs | 10 +--------- 4 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 library/core/src/io/write.rs diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 44a1d1b71d164..91bf95ab589be 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -18,4 +18,6 @@ pub use core::io::{ }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use core::io::{IoHandle, OsFunctions, SizeHint, chain, stream_len_default, take}; +pub use core::io::{ + IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, stream_len_default, take, +}; diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 1c27e42229bb7..36ec7d8d2adff 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -8,6 +8,7 @@ mod io_slice; mod seek; mod size_hint; mod util; +mod write; #[unstable(feature = "core_io_borrowed_buf", issue = "117693")] pub use self::borrowed_buf::{BorrowedBuf, BorrowedCursor}; @@ -32,6 +33,7 @@ pub use self::{ seek::stream_len_default, size_hint::SizeHint, util::{chain, take}, + write::default_write_vectored, }; /// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs new file mode 100644 index 0000000000000..cd0d2eb37249b --- /dev/null +++ b/library/core/src/io/write.rs @@ -0,0 +1,11 @@ +use crate::io::{IoSlice, Result}; + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn default_write_vectored(write: F, bufs: &[IoSlice<'_>]) -> Result +where + F: FnOnce(&[u8]) -> Result, +{ + let buf = bufs.iter().find(|b| !b.is_empty()).map_or(&[][..], |b| &**b); + write(buf) +} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 38c8120d66fc6..f2f3aa8bf8dd9 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -312,7 +312,7 @@ pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; pub use alloc_crate::io::{ Chain, Empty, Error, ErrorKind, Repeat, Result, Seek, SeekFrom, Sink, Take, empty, repeat, sink, }; -pub(crate) use alloc_crate::io::{IoHandle, stream_len_default}; +pub(crate) use alloc_crate::io::{IoHandle, default_write_vectored, stream_len_default}; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; use alloc_crate::io::{OsFunctions, SizeHint}; @@ -549,14 +549,6 @@ where read(buf) } -pub(crate) fn default_write_vectored(write: F, bufs: &[IoSlice<'_>]) -> Result -where - F: FnOnce(&[u8]) -> Result, -{ - let buf = bufs.iter().find(|b| !b.is_empty()).map_or(&[][..], |b| &**b); - write(buf) -} - pub(crate) fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { while !buf.is_empty() { match this.read(buf) { From 55585ea8b44d6c676b24a5ada113f25a312242e7 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 22 May 2026 13:58:21 +1000 Subject: [PATCH 45/78] Move `std::io::cursor`internals to `core::io` --- library/alloc/src/io/mod.rs | 3 +- library/core/src/io/cursor.rs | 59 ++++++++++++++++++++++++++++++++++- library/core/src/io/mod.rs | 1 + library/std/src/io/cursor.rs | 52 +++--------------------------- 4 files changed, 65 insertions(+), 50 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 91bf95ab589be..6b05d38125654 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -19,5 +19,6 @@ pub use core::io::{ #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use core::io::{ - IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, stream_len_default, take, + IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, slice_write, slice_write_all, + slice_write_all_vectored, slice_write_vectored, stream_len_default, take, }; diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index c104b426f48df..7ee77a0419fda 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -1,4 +1,5 @@ -use crate::io::{self, ErrorKind, SeekFrom}; +use crate::cmp; +use crate::io::{self, ErrorKind, IoSlice, SeekFrom}; /// A `Cursor` wraps an in-memory buffer and provides it with a /// [`Seek`] implementation. @@ -294,6 +295,62 @@ where } } +// Non-resizing write implementation +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result { + let pos = cmp::min(*pos_mut, slice.len() as u64); + let dst = &mut slice[(pos as usize)..]; + let amt = cmp::min(buf.len(), dst.len()); + dst[..amt].copy_from_slice(&buf[..amt]); + *pos_mut += amt as u64; + Ok(amt) +} + +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write_vectored( + pos_mut: &mut u64, + slice: &mut [u8], + bufs: &[IoSlice<'_>], +) -> io::Result { + let mut nwritten = 0; + for buf in bufs { + let n = slice_write(pos_mut, slice, buf)?; + nwritten += n; + if n < buf.len() { + break; + } + } + Ok(nwritten) +} + +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write_all(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result<()> { + let n = slice_write(pos_mut, slice, buf)?; + if n < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } +} + +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write_all_vectored( + pos_mut: &mut u64, + slice: &mut [u8], + bufs: &[IoSlice<'_>], +) -> io::Result<()> { + for buf in bufs { + let n = slice_write(pos_mut, slice, buf)?; + if n < buf.len() { + return Err(io::Error::WRITE_ALL_EOF); + } + } + Ok(()) +} #[stable(feature = "rust1", since = "1.0.0")] impl io::Seek for Cursor where diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 36ec7d8d2adff..92bd55a01538e 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -29,6 +29,7 @@ pub use self::{ #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ + cursor::{slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored}, error::{Custom, CustomOwner, OsFunctions}, seek::stream_len_default, size_hint::SizeHint, diff --git a/library/std/src/io/cursor.rs b/library/std/src/io/cursor.rs index 5399789b4242f..87b721cf1d4b1 100644 --- a/library/std/src/io/cursor.rs +++ b/library/std/src/io/cursor.rs @@ -4,8 +4,11 @@ mod tests; #[stable(feature = "rust1", since = "1.0.0")] pub use core::io::Cursor; +use alloc_crate::io::{ + slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, +}; + use crate::alloc::Allocator; -use crate::cmp; use crate::io::prelude::*; use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut}; @@ -150,53 +153,6 @@ impl Write for Cursor { } } -// Non-resizing write implementation -#[inline] -fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result { - let pos = cmp::min(*pos_mut, slice.len() as u64); - let amt = (&mut slice[(pos as usize)..]).write(buf)?; - *pos_mut += amt as u64; - Ok(amt) -} - -#[inline] -fn slice_write_vectored( - pos_mut: &mut u64, - slice: &mut [u8], - bufs: &[IoSlice<'_>], -) -> io::Result { - let mut nwritten = 0; - for buf in bufs { - let n = slice_write(pos_mut, slice, buf)?; - nwritten += n; - if n < buf.len() { - break; - } - } - Ok(nwritten) -} - -#[inline] -fn slice_write_all(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result<()> { - let n = slice_write(pos_mut, slice, buf)?; - if n < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } -} - -#[inline] -fn slice_write_all_vectored( - pos_mut: &mut u64, - slice: &mut [u8], - bufs: &[IoSlice<'_>], -) -> io::Result<()> { - for buf in bufs { - let n = slice_write(pos_mut, slice, buf)?; - if n < buf.len() { - return Err(io::Error::WRITE_ALL_EOF); - } - } - Ok(()) -} - /// Reserves the required space, and pads the vec with 0s if necessary. fn reserve_and_pad( pos_mut: &mut u64, From 66f79f18cd581438c1ed0d80c443baf64c10a14e Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 3 Jul 2026 11:09:32 +1000 Subject: [PATCH 46/78] Move `WriteThroughCursor` internals to `alloc::io` --- library/alloc/src/io/cursor.rs | 144 +++++++++++++++++++++++++++++++++ library/alloc/src/io/mod.rs | 5 ++ library/std/src/io/cursor.rs | 131 +----------------------------- 3 files changed, 152 insertions(+), 128 deletions(-) create mode 100644 library/alloc/src/io/cursor.rs diff --git a/library/alloc/src/io/cursor.rs b/library/alloc/src/io/cursor.rs new file mode 100644 index 0000000000000..a75d6e7df76ae --- /dev/null +++ b/library/alloc/src/io/cursor.rs @@ -0,0 +1,144 @@ +use crate::alloc::Allocator; +use crate::io::{self, ErrorKind, IoSlice}; +use crate::vec::Vec; + +/// Reserves the required space, and pads the vec with 0s if necessary. +fn reserve_and_pad( + pos_mut: &mut u64, + vec: &mut Vec, + buf_len: usize, +) -> io::Result { + let pos: usize = (*pos_mut).try_into().map_err(|_| { + io::const_error!( + ErrorKind::InvalidInput, + "cursor position exceeds maximum possible vector length", + ) + })?; + + // For safety reasons, we don't want these numbers to overflow + // otherwise our allocation won't be enough + let desired_cap = pos.saturating_add(buf_len); + if desired_cap > vec.capacity() { + // We want our vec's total capacity + // to have room for (pos+buf_len) bytes. Reserve allocates + // based on additional elements from the length, so we need to + // reserve the difference + cfg_select! { + no_global_oom_handling => { + vec.try_reserve(desired_cap - vec.len())?; + } + _ => { + vec.reserve(desired_cap - vec.len()); + } + } + } + // Pad if pos is above the current len. + if pos > vec.len() { + let diff = pos - vec.len(); + // Unfortunately, `resize()` would suffice but the optimiser does not + // realise the `reserve` it does can be eliminated. So we do it manually + // to eliminate that extra branch + let spare = vec.spare_capacity_mut(); + debug_assert!(spare.len() >= diff); + // Safety: we have allocated enough capacity for this. + // And we are only writing, not reading + unsafe { + spare.get_unchecked_mut(..diff).fill(core::mem::MaybeUninit::new(0)); + vec.set_len(pos); + } + } + + Ok(pos) +} + +/// Writes the slice to the vec without allocating. +/// +/// # Safety +/// +/// `vec` must have `buf.len()` spare capacity. +unsafe fn vec_write_all_unchecked(pos: usize, vec: &mut Vec, buf: &[u8]) -> usize +where + A: Allocator, +{ + debug_assert!(vec.capacity() >= pos + buf.len()); + unsafe { vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len()) }; + pos + buf.len() +} + +/// Resizing `write_all` implementation for [`Cursor`]. +/// +/// Cursor is allowed to have a pre-allocated and initialised +/// vector body, but with a position of 0. This means the `Write` +/// will overwrite the contents of the vec. +/// +/// This also allows for the vec body to be empty, but with a position of N. +/// This means that `Write` will pad the vec with 0 initially, +/// before writing anything from that point +/// +/// [`Cursor`]: crate::io::Cursor +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result +where + A: Allocator, +{ + let buf_len = buf.len(); + let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; + + // Write the buf then progress the vec forward if necessary + // Safety: we have ensured that the capacity is available + // and that all bytes get written up to pos + unsafe { + pos = vec_write_all_unchecked(pos, vec, buf); + if pos > vec.len() { + vec.set_len(pos); + } + }; + + // Bump us forward + *pos_mut += buf_len as u64; + Ok(buf_len) +} + +/// Resizing `write_all_vectored` implementation for [`Cursor`]. +/// +/// Cursor is allowed to have a pre-allocated and initialised +/// vector body, but with a position of 0. This means the `Write` +/// will overwrite the contents of the vec. +/// +/// This also allows for the vec body to be empty, but with a position of N. +/// This means that `Write` will pad the vec with 0 initially, +/// before writing anything from that point +/// +/// [`Cursor`]: crate::io::Cursor +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn vec_write_all_vectored( + pos_mut: &mut u64, + vec: &mut Vec, + bufs: &[IoSlice<'_>], +) -> io::Result +where + A: Allocator, +{ + // For safety reasons, we don't want this sum to overflow ever. + // If this saturates, the reserve should panic to avoid any unsound writing. + let buf_len = bufs.iter().fold(0usize, |a, b| a.saturating_add(b.len())); + let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; + + // Write the buf then progress the vec forward if necessary + // Safety: we have ensured that the capacity is available + // and that all bytes get written up to the last pos + unsafe { + for buf in bufs { + pos = vec_write_all_unchecked(pos, vec, buf); + } + if pos > vec.len() { + vec.set_len(pos); + } + } + + // Bump us forward + *pos_mut += buf_len as u64; + Ok(buf_len) +} diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 6b05d38125654..0830ac8d5a72e 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -1,5 +1,6 @@ //! Traits, helpers, and type definitions for core I/O functionality. +mod cursor; mod error; mod impls; @@ -22,3 +23,7 @@ pub use core::io::{ IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, stream_len_default, take, }; + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub use self::cursor::{vec_write_all, vec_write_all_vectored}; diff --git a/library/std/src/io/cursor.rs b/library/std/src/io/cursor.rs index 87b721cf1d4b1..69b1a3311fe21 100644 --- a/library/std/src/io/cursor.rs +++ b/library/std/src/io/cursor.rs @@ -5,12 +5,13 @@ mod tests; pub use core::io::Cursor; use alloc_crate::io::{ - slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, + slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, vec_write_all, + vec_write_all_vectored, }; use crate::alloc::Allocator; use crate::io::prelude::*; -use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut}; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; #[stable(feature = "rust1", since = "1.0.0")] impl Read for Cursor @@ -153,132 +154,6 @@ impl Write for Cursor { } } -/// Reserves the required space, and pads the vec with 0s if necessary. -fn reserve_and_pad( - pos_mut: &mut u64, - vec: &mut Vec, - buf_len: usize, -) -> io::Result { - let pos: usize = (*pos_mut).try_into().map_err(|_| { - io::const_error!( - ErrorKind::InvalidInput, - "cursor position exceeds maximum possible vector length", - ) - })?; - - // For safety reasons, we don't want these numbers to overflow - // otherwise our allocation won't be enough - let desired_cap = pos.saturating_add(buf_len); - if desired_cap > vec.capacity() { - // We want our vec's total capacity - // to have room for (pos+buf_len) bytes. Reserve allocates - // based on additional elements from the length, so we need to - // reserve the difference - vec.reserve(desired_cap - vec.len()); - } - // Pad if pos is above the current len. - if pos > vec.len() { - let diff = pos - vec.len(); - // Unfortunately, `resize()` would suffice but the optimiser does not - // realise the `reserve` it does can be eliminated. So we do it manually - // to eliminate that extra branch - let spare = vec.spare_capacity_mut(); - debug_assert!(spare.len() >= diff); - // Safety: we have allocated enough capacity for this. - // And we are only writing, not reading - unsafe { - spare.get_unchecked_mut(..diff).fill(core::mem::MaybeUninit::new(0)); - vec.set_len(pos); - } - } - - Ok(pos) -} - -/// Writes the slice to the vec without allocating. -/// -/// # Safety -/// -/// `vec` must have `buf.len()` spare capacity. -unsafe fn vec_write_all_unchecked(pos: usize, vec: &mut Vec, buf: &[u8]) -> usize -where - A: Allocator, -{ - debug_assert!(vec.capacity() >= pos + buf.len()); - unsafe { vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len()) }; - pos + buf.len() -} - -/// Resizing `write_all` implementation for [`Cursor`]. -/// -/// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the [`Write`] -/// will overwrite the contents of the vec. -/// -/// This also allows for the vec body to be empty, but with a position of N. -/// This means that [`Write`] will pad the vec with 0 initially, -/// before writing anything from that point -fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result -where - A: Allocator, -{ - let buf_len = buf.len(); - let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; - - // Write the buf then progress the vec forward if necessary - // Safety: we have ensured that the capacity is available - // and that all bytes get written up to pos - unsafe { - pos = vec_write_all_unchecked(pos, vec, buf); - if pos > vec.len() { - vec.set_len(pos); - } - }; - - // Bump us forward - *pos_mut += buf_len as u64; - Ok(buf_len) -} - -/// Resizing `write_all_vectored` implementation for [`Cursor`]. -/// -/// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the [`Write`] -/// will overwrite the contents of the vec. -/// -/// This also allows for the vec body to be empty, but with a position of N. -/// This means that [`Write`] will pad the vec with 0 initially, -/// before writing anything from that point -fn vec_write_all_vectored( - pos_mut: &mut u64, - vec: &mut Vec, - bufs: &[IoSlice<'_>], -) -> io::Result -where - A: Allocator, -{ - // For safety reasons, we don't want this sum to overflow ever. - // If this saturates, the reserve should panic to avoid any unsound writing. - let buf_len = bufs.iter().fold(0usize, |a, b| a.saturating_add(b.len())); - let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; - - // Write the buf then progress the vec forward if necessary - // Safety: we have ensured that the capacity is available - // and that all bytes get written up to the last pos - unsafe { - for buf in bufs { - pos = vec_write_all_unchecked(pos, vec, buf); - } - if pos > vec.len() { - vec.set_len(pos); - } - } - - // Bump us forward - *pos_mut += buf_len as u64; - Ok(buf_len) -} - #[stable(feature = "rust1", since = "1.0.0")] impl Write for Cursor<&mut [u8]> { #[inline] From ec0534fc4ce9686d01c3ab916d3653bc95ec7b39 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 3 Jul 2026 11:37:13 +1000 Subject: [PATCH 47/78] Move `std::io::Write` to `core::io` --- library/alloc/src/io/cursor.rs | 142 +++++++- library/alloc/src/io/impls.rs | 199 ++++++++++- library/alloc/src/io/mod.rs | 11 +- library/alloc/src/lib.rs | 2 + library/core/src/io/cursor.rs | 127 ++++++- library/core/src/io/impls.rs | 147 +++++++- library/core/src/io/mod.rs | 8 +- library/core/src/io/util.rs | 158 ++++++++- library/core/src/io/write.rs | 408 +++++++++++++++++++++- library/std/src/io/cursor.rs | 246 +------------- library/std/src/io/impls.rs | 311 +---------------- library/std/src/io/mod.rs | 409 +---------------------- library/std/src/io/util.rs | 161 +-------- library/std/src/lib.rs | 2 + tests/ui/suggestions/issue-105645.stderr | 2 +- 15 files changed, 1188 insertions(+), 1145 deletions(-) diff --git a/library/alloc/src/io/cursor.rs b/library/alloc/src/io/cursor.rs index a75d6e7df76ae..21f6b8b5b3ffd 100644 --- a/library/alloc/src/io/cursor.rs +++ b/library/alloc/src/io/cursor.rs @@ -1,5 +1,9 @@ use crate::alloc::Allocator; -use crate::io::{self, ErrorKind, IoSlice}; +use crate::boxed::Box; +use crate::io::{ + self, Cursor, ErrorKind, IoSlice, WriteThroughCursor, slice_write, slice_write_all, + slice_write_all_vectored, slice_write_vectored, +}; use crate::vec::Vec; /// Reserves the required space, and pads the vec with 0s if necessary. @@ -68,17 +72,15 @@ where /// Resizing `write_all` implementation for [`Cursor`]. /// /// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the `Write` +/// vector body, but with a position of 0. This means the [`Write`] /// will overwrite the contents of the vec. /// /// This also allows for the vec body to be empty, but with a position of N. -/// This means that `Write` will pad the vec with 0 initially, +/// This means that [`Write`] will pad the vec with 0 initially, /// before writing anything from that point /// -/// [`Cursor`]: crate::io::Cursor -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result +/// [`Write`]: crate::io::Write +fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result where A: Allocator, { @@ -103,17 +105,15 @@ where /// Resizing `write_all_vectored` implementation for [`Cursor`]. /// /// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the `Write` +/// vector body, but with a position of 0. This means the [`Write`] /// will overwrite the contents of the vec. /// /// This also allows for the vec body to be empty, but with a position of N. -/// This means that `Write` will pad the vec with 0 initially, +/// This means that [`Write`] will pad the vec with 0 initially, /// before writing anything from that point /// -/// [`Cursor`]: crate::io::Cursor -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn vec_write_all_vectored( +/// [`Write`]: crate::io::Write +fn vec_write_all_vectored( pos_mut: &mut u64, vec: &mut Vec, bufs: &[IoSlice<'_>], @@ -142,3 +142,119 @@ where *pos_mut += buf_len as u64; Ok(buf_len) } + +#[stable(feature = "cursor_mut_vec", since = "1.25.0")] +impl WriteThroughCursor for &mut Vec +where + A: Allocator, +{ + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf) + } + + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(_this: &Cursor) -> bool { + true + } + + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf)?; + Ok(()) + } + + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs)?; + Ok(()) + } + + #[inline] + fn flush(_this: &mut Cursor) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl WriteThroughCursor for Vec +where + A: Allocator, +{ + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf) + } + + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(_this: &Cursor) -> bool { + true + } + + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf)?; + Ok(()) + } + + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs)?; + Ok(()) + } + + #[inline] + fn flush(_this: &mut Cursor) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "cursor_box_slice", since = "1.5.0")] +impl WriteThroughCursor for Box<[u8], A> +where + A: Allocator, +{ + #[inline] + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + slice_write(pos, inner, buf) + } + + #[inline] + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + slice_write_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(_this: &Cursor) -> bool { + true + } + + #[inline] + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + slice_write_all(pos, inner, buf) + } + + #[inline] + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + slice_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn flush(_this: &mut Cursor) -> io::Result<()> { + Ok(()) + } +} diff --git a/library/alloc/src/io/impls.rs b/library/alloc/src/io/impls.rs index 732842bc65df7..0f6bfe244dba2 100644 --- a/library/alloc/src/io/impls.rs +++ b/library/alloc/src/io/impls.rs @@ -1,7 +1,12 @@ +use crate::alloc::Allocator; use crate::boxed::Box; -use crate::io::{self, Seek, SeekFrom, SizeHint}; +#[cfg(not(no_global_oom_handling))] +use crate::collections::VecDeque; +use crate::fmt; +use crate::io::{self, IoSlice, Seek, SeekFrom, SizeHint, Write}; #[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))] use crate::sync::Arc; +use crate::vec::Vec; // ============================================================================= // Forwarding implementations @@ -20,6 +25,43 @@ impl SizeHint for Box { } } +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + (**self).write_all_vectored(bufs) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} #[stable(feature = "rust1", since = "1.0.0")] impl Seek for Box { #[inline] @@ -51,6 +93,161 @@ impl Seek for Box { // ============================================================================= // In-memory buffer implementations +/// Write is implemented for `Vec` by appending to the vector. +/// The vector will grow as needed. +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + ::write_all(self, buf)?; + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let len = bufs.iter().map(|b| b.len()).sum(); + cfg_select! { + no_global_oom_handling => { + self.try_reserve(len)?; + } + _ => { + self.reserve(len); + } + } + for buf in bufs { + ::write_all(self, buf)?; + } + Ok(len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + cfg_select! { + no_global_oom_handling => { + let n = buf.len(); + self.try_reserve(n)?; + // SAFETY: + // * self and buf are non-overlapping + // * self[..len] is already initialized + // * self[len..len + n] is initialized by copy_nonoverlapping + // * len + n is within the capacity of self based on the reservation completed above + unsafe { + let len = self.len(); + let src = buf.as_ptr(); + let dst = self.as_mut_ptr().add(len); + core::ptr::copy_nonoverlapping(src, dst, n); + self.set_len(len + n); + } + } + _ => { + self.extend_from_slice(buf); + } + } + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + self.write_vectored(bufs)?; + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Write is implemented for `VecDeque` by appending to the `VecDeque`, growing it as needed. +#[cfg(not(no_global_oom_handling))] +#[stable(feature = "vecdeque_read_write", since = "1.63.0")] +impl Write for VecDeque { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend(buf); + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let len = bufs.iter().map(|b| b.len()).sum(); + self.reserve(len); + for buf in bufs { + self.extend(&**buf); + } + Ok(len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.extend(buf); + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + self.write_vectored(bufs)?; + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))] +#[stable(feature = "io_traits_arc", since = "1.73.0")] +impl Write for Arc +where + for<'a> &'a W: Write, + W: crate::io::IoHandle, +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (&**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (&**self).write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + (&**self).is_write_vectored() + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (&**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (&**self).write_all(buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + (&**self).write_all_vectored(bufs) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (&**self).write_fmt(fmt) + } +} #[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))] #[stable(feature = "io_traits_arc", since = "1.73.0")] impl Seek for Arc diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 0830ac8d5a72e..678934b91da71 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -15,15 +15,12 @@ pub use core::io::{BorrowedBuf, BorrowedCursor}; #[unstable(feature = "alloc_io", issue = "154046")] pub use core::io::{ Chain, Cursor, Empty, Error, ErrorKind, IoSlice, IoSliceMut, Repeat, Result, Seek, SeekFrom, - Sink, Take, empty, repeat, sink, + Sink, Take, Write, empty, repeat, sink, }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use core::io::{ - IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, slice_write, slice_write_all, - slice_write_all_vectored, slice_write_vectored, stream_len_default, take, + IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_fmt, + default_write_vectored, slice_write, slice_write_all, slice_write_all_vectored, + slice_write_vectored, stream_len_default, take, }; - -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use self::cursor::{vec_write_all, vec_write_all_vectored}; diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 01b4e6f938616..9f82f5be82a68 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -96,6 +96,7 @@ #![feature(async_iterator)] #![feature(bstr)] #![feature(bstr_internals)] +#![feature(can_vector)] #![feature(case_ignorable)] #![feature(cast_maybe_uninit)] #![feature(cell_get_cloned)] @@ -180,6 +181,7 @@ #![feature(unicode_internals)] #![feature(unsize)] #![feature(unwrap_infallible)] +#![feature(write_all_vectored)] #![feature(wtf8_internals)] // tidy-alphabetical-end // diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index 7ee77a0419fda..15b313142c963 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -1,5 +1,5 @@ use crate::cmp; -use crate::io::{self, ErrorKind, IoSlice, SeekFrom}; +use crate::io::{self, ErrorKind, IoSlice, SeekFrom, Write}; /// A `Cursor` wraps an in-memory buffer and provides it with a /// [`Seek`] implementation. @@ -351,6 +351,81 @@ pub fn slice_write_all_vectored( } Ok(()) } + +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Cursor<&mut [u8]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write(pos, inner, buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all(pos, inner, buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "cursor_array", since = "1.61.0")] +impl Write for Cursor<[u8; N]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write(pos, inner, buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all(pos, inner, buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl io::Seek for Cursor where @@ -385,3 +460,53 @@ where Ok(self.position()) } } + +/// Trait used to allow indirect implementation of `Write` for `Cursor`. +/// Since [`Cursor`] is not a foundational type, it is not possible to implement +/// `Write` for `Cursor` if `Write` is defined in `libcore` and `T` is in a +/// downstream crate (e.g., `liballoc` or `libstd`). +/// +/// Methods are identical in purpose and meaning to their `Write` namesakes. +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub trait WriteThroughCursor: Sized { + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result; + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result; + fn is_write_vectored(this: &Cursor) -> bool; + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()>; + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()>; + fn flush(this: &mut Cursor) -> io::Result<()>; +} + +#[doc(hidden)] +impl Write for Cursor { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + WriteThroughCursor::write(self, buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + WriteThroughCursor::write_vectored(self, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + WriteThroughCursor::is_write_vectored(self) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + WriteThroughCursor::write_all(self, buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + WriteThroughCursor::write_all_vectored(self, bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + WriteThroughCursor::flush(self) + } +} diff --git a/library/core/src/io/impls.rs b/library/core/src/io/impls.rs index e8c716f060119..218bd7adc2a55 100644 --- a/library/core/src/io/impls.rs +++ b/library/core/src/io/impls.rs @@ -1,4 +1,5 @@ -use crate::io::{self, Seek, SeekFrom, SizeHint}; +use crate::io::{self, IoSlice, Seek, SeekFrom, SizeHint, Write}; +use crate::{cmp, fmt, mem}; // ============================================================================= // Forwarding implementations @@ -17,6 +18,43 @@ impl SizeHint for &mut T { } } +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for &mut W { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + (**self).write_all_vectored(bufs) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} #[stable(feature = "rust1", since = "1.0.0")] impl Seek for &mut S { #[inline] @@ -61,3 +99,110 @@ impl SizeHint for &[u8] { Some(self.len()) } } + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, data: &[u8]) -> io::Result { + let amt = cmp::min(data.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&data[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let mut nwritten = 0; + for buf in bufs { + nwritten += self.write(buf)?; + if self.is_empty() { + break; + } + } + + Ok(nwritten) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, data: &[u8]) -> io::Result<()> { + if self.write(data)? < data.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + for buf in bufs { + if self.write(buf)? < buf.len() { + return Err(io::Error::WRITE_ALL_EOF); + } + } + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[unstable(feature = "read_buf", issue = "78485")] +impl<'a> io::Write for core::io::BorrowedCursor<'a, u8> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let amt = cmp::min(buf.len(), self.capacity()); + self.append(&buf[..amt]); + Ok(amt) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let mut nwritten = 0; + for buf in bufs { + let n = self.write(buf)?; + nwritten += n; + if n < buf.len() { + break; + } + } + Ok(nwritten) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + if self.write(buf)? < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + for buf in bufs { + if self.write(buf)? < buf.len() { + return Err(io::Error::WRITE_ALL_EOF); + } + } + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 92bd55a01538e..98680ad390afc 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -25,16 +25,20 @@ pub use self::{ io_slice::{IoSlice, IoSliceMut}, seek::{Seek, SeekFrom}, util::{Chain, Empty, Repeat, Sink, Take, empty, repeat, sink}, + write::Write, }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ - cursor::{slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored}, + cursor::{ + WriteThroughCursor, slice_write, slice_write_all, slice_write_all_vectored, + slice_write_vectored, + }, error::{Custom, CustomOwner, OsFunctions}, seek::stream_len_default, size_hint::SizeHint, util::{chain, take}, - write::default_write_vectored, + write::{default_write_fmt, default_write_vectored}, }; /// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically diff --git a/library/core/src/io/util.rs b/library/core/src/io/util.rs index de37690436c80..4ffd38a906f1c 100644 --- a/library/core/src/io/util.rs +++ b/library/core/src/io/util.rs @@ -1,4 +1,4 @@ -use crate::io::{ErrorKind, Result, Seek, SeekFrom, SizeHint}; +use crate::io::{self, ErrorKind, IoSlice, Result, Seek, SeekFrom, SizeHint, Write}; use crate::{cmp, fmt}; /// `Empty` ignores any data written via [`Write`], and will always be empty @@ -23,6 +23,84 @@ impl SizeHint for Empty { } } +#[stable(feature = "empty_write", since = "1.73.0")] +impl Write for Empty { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "empty_write", since = "1.73.0")] +impl Write for &Empty { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + #[stable(feature = "empty_seek", since = "1.51.0")] impl Seek for Empty { #[inline] @@ -142,6 +220,84 @@ impl fmt::Debug for Repeat { #[derive(Copy, Clone, Debug, Default)] pub struct Sink; +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Sink { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "write_mt", since = "1.48.0")] +impl Write for &Sink { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + /// Creates an instance of a writer which will successfully consume all data. /// /// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index cd0d2eb37249b..91d3d2c2f6787 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -1,4 +1,370 @@ -use crate::io::{IoSlice, Result}; +use crate::fmt; +use crate::io::{Error, IoSlice, Result}; + +/// A trait for objects which are byte-oriented sinks. +/// +/// Implementors of the `Write` trait are sometimes called 'writers'. +/// +/// Writers are defined by two required methods, [`write`] and [`flush`]: +/// +/// * The [`write`] method will attempt to write some data into the object, +/// returning how many bytes were successfully written. +/// +/// * The [`flush`] method is useful for adapters and explicit buffers +/// themselves for ensuring that all buffered data has been pushed out to the +/// 'true sink'. +/// +/// Writers are intended to be composable with one another. Many implementors +/// throughout [`std::io`] take and provide types which implement the `Write` +/// trait. +/// +/// [`write`]: Write::write +/// [`flush`]: Write::flush +/// [`std::io`]: crate::io +/// +/// # Examples +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::fs::File; +/// +/// fn main() -> std::io::Result<()> { +/// let data = b"some bytes"; +/// +/// let mut pos = 0; +/// let mut buffer = File::create("foo.txt")?; +/// +/// while pos < data.len() { +/// let bytes_written = buffer.write(&data[pos..])?; +/// pos += bytes_written; +/// } +/// Ok(()) +/// } +/// ``` +/// +/// The trait also provides convenience methods like [`write_all`], which calls +/// `write` in a loop until its entire input has been written. +/// +/// [`write_all`]: Write::write_all +#[stable(feature = "rust1", since = "1.0.0")] +#[doc(notable_trait)] +#[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] +pub trait Write { + /// Writes a buffer into this writer, returning how many bytes were written. + /// + /// This function will attempt to write the entire contents of `buf`, but + /// the entire write might not succeed, or the write may also generate an + /// error. Typically, a call to `write` represents one attempt to write to + /// any wrapped object. + /// + /// Calls to `write` are not guaranteed to block waiting for data to be + /// written, and a write which would otherwise block can be indicated through + /// an [`Err`] variant. + /// + /// If this method consumed `n > 0` bytes of `buf` it must return [`Ok(n)`]. + /// If the return value is `Ok(n)` then `n` must satisfy `n <= buf.len()`. + /// A return value of `Ok(0)` typically means that the underlying object is + /// no longer able to accept bytes and will likely not be able to in the + /// future as well, or that the buffer provided is empty. + /// + /// # Errors + /// + /// Each call to `write` may generate an I/O error indicating that the + /// operation could not be completed. If an error is returned then no bytes + /// in the buffer were written to this writer. + /// + /// It is **not** considered an error if the entire buffer could not be + /// written to this writer. + /// + /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the + /// write operation should be retried if there is nothing else to do. + /// + /// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Writes some prefix of the byte string, not necessarily all of it. + /// buffer.write(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + /// + /// [`Ok(n)`]: Ok + #[stable(feature = "rust1", since = "1.0.0")] + fn write(&mut self, buf: &[u8]) -> Result; + + /// Like [`write`], except that it writes from a slice of buffers. + /// + /// Data is copied from each buffer in order, with the final buffer + /// read from possibly being only partially consumed. This method must + /// behave as a call to [`write`] with the buffers concatenated would. + /// + /// The default implementation calls [`write`] with either the first nonempty + /// buffer provided, or an empty one if none exists. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::IoSlice; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let data1 = [1; 8]; + /// let data2 = [15; 8]; + /// let io_slice1 = IoSlice::new(&data1); + /// let io_slice2 = IoSlice::new(&data2); + /// + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Writes some prefix of the byte string, not necessarily all of it. + /// buffer.write_vectored(&[io_slice1, io_slice2])?; + /// Ok(()) + /// } + /// ``` + /// + /// [`write`]: Write::write + #[stable(feature = "iovec", since = "1.36.0")] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { + default_write_vectored(|b| self.write(b), bufs) + } + + /// Determines if this `Write`r has an efficient [`write_vectored`] + /// implementation. + /// + /// If a `Write`r does not override the default [`write_vectored`] + /// implementation, code using it may want to avoid the method all together + /// and coalesce writes into a single buffer for higher performance. + /// + /// The default implementation returns `false`. + /// + /// [`write_vectored`]: Write::write_vectored + #[unstable(feature = "can_vector", issue = "69941")] + fn is_write_vectored(&self) -> bool { + false + } + + /// Flushes this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + /// + /// # Errors + /// + /// It is considered an error if not all bytes could be written due to + /// I/O errors or EOF being reached. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::io::BufWriter; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = BufWriter::new(File::create("foo.txt")?); + /// + /// buffer.write_all(b"some bytes")?; + /// buffer.flush()?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn flush(&mut self) -> Result<()>; + + /// Attempts to write an entire buffer into this writer. + /// + /// This method will continuously call [`write`] until there is no more data + /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is + /// returned. This method will not return until the entire buffer has been + /// successfully written or such an error occurs. The first error that is + /// not of [`ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted + /// + /// If the buffer contains no data, this will never call [`write`]. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`ErrorKind::Interrupted`] kind that [`write`] returns. + /// + /// [`write`]: Write::write + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// buffer.write_all(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + return Err(Error::WRITE_ALL_EOF); + } + Ok(n) => buf = &buf[n..], + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Attempts to write multiple buffers into this writer. + /// + /// This method will continuously call [`write_vectored`] until there is no + /// more data to be written or an error of non-[`ErrorKind::Interrupted`] + /// kind is returned. This method will not return until all buffers have + /// been successfully written or such an error occurs. The first error that + /// is not of [`ErrorKind::Interrupted`] kind generated from this method + /// will be returned. + /// + /// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted + /// + /// If the buffer contains no data, this will never call [`write_vectored`]. + /// + /// # Notes + /// + /// Unlike [`write_vectored`], this takes a *mutable* reference to + /// a slice of [`IoSlice`]s, not an immutable one. That's because we need to + /// modify the slice to keep track of the bytes already written. + /// + /// Once this function returns, the contents of `bufs` are unspecified, as + /// this depends on how many calls to [`write_vectored`] were necessary. It is + /// best to understand this function as taking ownership of `bufs` and to + /// not use `bufs` afterwards. The underlying buffers, to which the + /// [`IoSlice`]s point (but not the [`IoSlice`]s themselves), are unchanged and + /// can be reused. + /// + /// [`write_vectored`]: Write::write_vectored + /// + /// # Examples + /// + /// ``` + /// #![feature(write_all_vectored)] + /// # fn main() -> std::io::Result<()> { + /// + /// use std::io::{Write, IoSlice}; + /// + /// let mut writer = Vec::new(); + /// let bufs = &mut [ + /// IoSlice::new(&[1]), + /// IoSlice::new(&[2, 3]), + /// IoSlice::new(&[4, 5, 6]), + /// ]; + /// + /// writer.write_all_vectored(bufs)?; + /// // Note: the contents of `bufs` is now undefined, see the Notes section. + /// + /// assert_eq!(writer, &[1, 2, 3, 4, 5, 6]); + /// # Ok(()) } + /// ``` + #[unstable(feature = "write_all_vectored", issue = "70436")] + fn write_all_vectored(&mut self, mut bufs: &mut [IoSlice<'_>]) -> Result<()> { + // Guarantee that bufs is empty if it contains no data, + // to avoid calling write_vectored if there is no data to be written. + IoSlice::advance_slices(&mut bufs, 0); + while !bufs.is_empty() { + match self.write_vectored(bufs) { + Ok(0) => { + return Err(Error::WRITE_ALL_EOF); + } + Ok(n) => IoSlice::advance_slices(&mut bufs, n), + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Writes a formatted string into this writer, returning any error + /// encountered. + /// + /// This method is primarily used to interface with the + /// [`format_args!()`] macro, and it is rare that this should + /// explicitly be called. The [`write!()`] macro should be favored to + /// invoke this method instead. + /// + /// This function internally uses the [`write_all`] method on + /// this trait and hence will continuously write data so long as no errors + /// are received. This also means that partial writes are not indicated in + /// this signature. + /// + /// [`write_all`]: Write::write_all + /// + /// # Errors + /// + /// This function will return any I/O error reported while formatting. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // this call + /// write!(buffer, "{:.*}", 2, 1.234567)?; + /// // turns into this: + /// buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> { + if let Some(s) = args.as_statically_known_str() { + self.write_all(s.as_bytes()) + } else { + default_write_fmt(self, args) + } + } + + /// Creates a "by reference" adapter for this instance of `Write`. + /// + /// The returned adapter also implements `Write` and will simply borrow this + /// current writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::Write; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// let reference = buffer.by_ref(); + /// + /// // we can use reference just like our original buffer + /// reference.write_all(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } +} #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -9,3 +375,43 @@ where let buf = bufs.iter().find(|b| !b.is_empty()).map_or(&[][..], |b| &**b); write(buf) } + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn default_write_fmt(this: &mut W, args: fmt::Arguments<'_>) -> Result<()> { + // Create a shim which translates a `Write` to a `fmt::Write` and saves off + // I/O errors, instead of discarding them. + struct Adapter<'a, T: ?Sized + 'a> { + inner: &'a mut T, + error: Result<()>, + } + + impl fmt::Write for Adapter<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adapter { inner: this, error: Ok(()) }; + match fmt::write(&mut output, args) { + Ok(()) => Ok(()), + Err(..) => { + // Check whether the error came from the underlying `Write`. + if output.error.is_err() { + output.error + } else { + // This shouldn't happen: the underlying stream did not error, + // but somehow the formatter still errored? + panic!( + "a formatting trait implementation returned an error when the underlying stream did not" + ); + } + } + } +} diff --git a/library/std/src/io/cursor.rs b/library/std/src/io/cursor.rs index 69b1a3311fe21..e0b127bef8e2b 100644 --- a/library/std/src/io/cursor.rs +++ b/library/std/src/io/cursor.rs @@ -4,14 +4,8 @@ mod tests; #[stable(feature = "rust1", since = "1.0.0")] pub use core::io::Cursor; -use alloc_crate::io::{ - slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, vec_write_all, - vec_write_all_vectored, -}; - -use crate::alloc::Allocator; use crate::io::prelude::*; -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::io::{self, BorrowedCursor, IoSliceMut}; #[stable(feature = "rust1", since = "1.0.0")] impl Read for Cursor @@ -105,241 +99,3 @@ where self.set_position(self.position() + amt as u64); } } - -/// Trait used to allow indirect implementation of `Write` for `Cursor`. -/// Since [`Cursor`] is not a foundational type, it is not possible to implement -/// `Write` for `Cursor` if `Write` is defined in `libcore` and `T` is in a -/// downstream crate (e.g., `liballoc` or `libstd`). -/// -/// Methods are identical in purpose and meaning to their `Write` namesakes. -trait WriteThroughCursor: Sized { - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result; - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result; - fn is_write_vectored(this: &Cursor) -> bool; - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()>; - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()>; - fn flush(this: &mut Cursor) -> io::Result<()>; -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Cursor { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - WriteThroughCursor::write(self, buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - WriteThroughCursor::write_vectored(self, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - WriteThroughCursor::is_write_vectored(self) - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - WriteThroughCursor::write_all(self, buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - WriteThroughCursor::write_all_vectored(self, bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - WriteThroughCursor::flush(self) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Cursor<&mut [u8]> { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write(pos, inner, buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all(pos, inner, buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "cursor_mut_vec", since = "1.25.0")] -impl WriteThroughCursor for &mut Vec -where - A: Allocator, -{ - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf) - } - - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(_this: &Cursor) -> bool { - true - } - - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf)?; - Ok(()) - } - - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs)?; - Ok(()) - } - - #[inline] - fn flush(_this: &mut Cursor) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl WriteThroughCursor for Vec -where - A: Allocator, -{ - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf) - } - - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(_this: &Cursor) -> bool { - true - } - - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf)?; - Ok(()) - } - - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs)?; - Ok(()) - } - - #[inline] - fn flush(_this: &mut Cursor) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "cursor_box_slice", since = "1.5.0")] -impl WriteThroughCursor for Box<[u8], A> -where - A: Allocator, -{ - #[inline] - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - slice_write(pos, inner, buf) - } - - #[inline] - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - slice_write_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(_this: &Cursor) -> bool { - true - } - - #[inline] - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - slice_write_all(pos, inner, buf) - } - - #[inline] - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - slice_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn flush(_this: &mut Cursor) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "cursor_array", since = "1.61.0")] -impl Write for Cursor<[u8; N]> { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write(pos, inner, buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all(pos, inner, buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/library/std/src/io/impls.rs b/library/std/src/io/impls.rs index 08bb34376b075..61f82828149aa 100644 --- a/library/std/src/io/impls.rs +++ b/library/std/src/io/impls.rs @@ -3,9 +3,9 @@ mod tests; use crate::alloc::Allocator; use crate::collections::VecDeque; -use crate::io::{self, BorrowedCursor, BufRead, IoSlice, IoSliceMut, Read, Write}; +use crate::io::{self, BorrowedCursor, BufRead, IoSliceMut, Read}; use crate::sync::Arc; -use crate::{cmp, fmt, mem, str}; +use crate::{cmp, str}; // ============================================================================= // Forwarding implementations @@ -53,43 +53,6 @@ impl Read for &mut R { } } #[stable(feature = "rust1", since = "1.0.0")] -impl Write for &mut W { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - (**self).write_vectored(bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - (**self).is_write_vectored() - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (**self).write_all(buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - (**self).write_all_vectored(bufs) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (**self).write_fmt(fmt) - } -} -#[stable(feature = "rust1", since = "1.0.0")] impl BufRead for &mut B { #[inline] fn fill_buf(&mut self) -> io::Result<&[u8]> { @@ -165,43 +128,6 @@ impl Read for Box { } } #[stable(feature = "rust1", since = "1.0.0")] -impl Write for Box { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - (**self).write_vectored(bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - (**self).is_write_vectored() - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (**self).write_all(buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - (**self).write_all_vectored(bufs) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (**self).write_fmt(fmt) - } -} -#[stable(feature = "rust1", since = "1.0.0")] impl BufRead for Box { #[inline] fn fill_buf(&mut self) -> io::Result<&[u8]> { @@ -362,108 +288,6 @@ impl BufRead for &[u8] { } } -/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting -/// its data. -/// -/// Note that writing updates the slice to point to the yet unwritten part. -/// The slice will be empty when it has been completely overwritten. -/// -/// If the number of bytes to be written exceeds the size of the slice, write operations will -/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of -/// kind `ErrorKind::WriteZero`. -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for &mut [u8] { - #[inline] - fn write(&mut self, data: &[u8]) -> io::Result { - let amt = cmp::min(data.len(), self.len()); - let (a, b) = mem::take(self).split_at_mut(amt); - a.copy_from_slice(&data[..amt]); - *self = b; - Ok(amt) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let mut nwritten = 0; - for buf in bufs { - nwritten += self.write(buf)?; - if self.is_empty() { - break; - } - } - - Ok(nwritten) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, data: &[u8]) -> io::Result<()> { - if self.write(data)? < data.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - for buf in bufs { - if self.write(buf)? < buf.len() { - return Err(io::Error::WRITE_ALL_EOF); - } - } - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Write is implemented for `Vec` by appending to the vector. -/// The vector will grow as needed. -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Vec { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.extend_from_slice(buf); - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let len = bufs.iter().map(|b| b.len()).sum(); - self.reserve(len); - for buf in bufs { - self.extend_from_slice(buf); - } - Ok(len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.extend_from_slice(buf); - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - self.write_vectored(bufs)?; - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - /// Read is implemented for `VecDeque` by consuming bytes from the front of the `VecDeque`. #[stable(feature = "vecdeque_read_write", since = "1.63.0")] impl Read for VecDeque { @@ -573,96 +397,6 @@ impl BufRead for VecDeque { } } -/// Write is implemented for `VecDeque` by appending to the `VecDeque`, growing it as needed. -#[stable(feature = "vecdeque_read_write", since = "1.63.0")] -impl Write for VecDeque { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.extend(buf); - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let len = bufs.iter().map(|b| b.len()).sum(); - self.reserve(len); - for buf in bufs { - self.extend(&**buf); - } - Ok(len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.extend(buf); - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - self.write_vectored(bufs)?; - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[unstable(feature = "read_buf", issue = "78485")] -impl<'a> io::Write for core::io::BorrowedCursor<'a, u8> { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - let amt = cmp::min(buf.len(), self.capacity()); - self.append(&buf[..amt]); - Ok(amt) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let mut nwritten = 0; - for buf in bufs { - let n = self.write(buf)?; - nwritten += n; - if n < buf.len() { - break; - } - } - Ok(nwritten) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - if self.write(buf)? < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - for buf in bufs { - if self.write(buf)? < buf.len() { - return Err(io::Error::WRITE_ALL_EOF); - } - } - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - #[stable(feature = "io_traits_arc", since = "1.73.0")] impl Read for Arc where @@ -709,44 +443,3 @@ where (&**self).read_buf_exact(cursor) } } -#[stable(feature = "io_traits_arc", since = "1.73.0")] -impl Write for Arc -where - for<'a> &'a W: Write, - W: crate::io::IoHandle, -{ - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (&**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - (&**self).write_vectored(bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - (&**self).is_write_vectored() - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (&**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (&**self).write_all(buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - (&**self).write_all_vectored(bufs) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (&**self).write_fmt(fmt) - } -} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index f2f3aa8bf8dd9..b1dbcf1f5bd46 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -306,13 +306,16 @@ pub use alloc_crate::io::RawOsError; pub use alloc_crate::io::SimpleMessage; #[unstable(feature = "io_const_error", issue = "133448")] pub use alloc_crate::io::const_error; +#[allow(unused_imports, reason = "only used by certain platforms")] +pub(crate) use alloc_crate::io::default_write_vectored; #[unstable(feature = "read_buf", issue = "78485")] pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::io::{ - Chain, Empty, Error, ErrorKind, Repeat, Result, Seek, SeekFrom, Sink, Take, empty, repeat, sink, + Chain, Empty, Error, ErrorKind, Repeat, Result, Seek, SeekFrom, Sink, Take, Write, empty, + repeat, sink, }; -pub(crate) use alloc_crate::io::{IoHandle, default_write_vectored, stream_len_default}; +pub(crate) use alloc_crate::io::{IoHandle, stream_len_default}; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; use alloc_crate::io::{OsFunctions, SizeHint}; @@ -338,7 +341,7 @@ pub use self::{ stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, }; use crate::mem::MaybeUninit; -use crate::{cmp, fmt, slice, str}; +use crate::{cmp, slice, str}; mod buffered; pub(crate) mod copy; @@ -592,47 +595,6 @@ pub(crate) fn default_read_buf_exact( Ok(()) } -pub(crate) fn default_write_fmt( - this: &mut W, - args: fmt::Arguments<'_>, -) -> Result<()> { - // Create a shim which translates a `Write` to a `fmt::Write` and saves off - // I/O errors, instead of discarding them. - struct Adapter<'a, T: ?Sized + 'a> { - inner: &'a mut T, - error: Result<()>, - } - - impl fmt::Write for Adapter<'_, T> { - fn write_str(&mut self, s: &str) -> fmt::Result { - match self.inner.write_all(s.as_bytes()) { - Ok(()) => Ok(()), - Err(e) => { - self.error = Err(e); - Err(fmt::Error) - } - } - } - } - - let mut output = Adapter { inner: this, error: Ok(()) }; - match fmt::write(&mut output, args) { - Ok(()) => Ok(()), - Err(..) => { - // Check whether the error came from the underlying `Write`. - if output.error.is_err() { - output.error - } else { - // This shouldn't happen: the underlying stream did not error, - // but somehow the formatter still errored? - panic!( - "a formatting trait implementation returned an error when the underlying stream did not" - ); - } - } - } -} - /// The `Read` trait allows for reading bytes from a source. /// /// Implementors of the `Read` trait are called 'readers'. @@ -1395,365 +1357,6 @@ pub fn read_to_string(mut reader: R) -> Result { Ok(buf) } -/// A trait for objects which are byte-oriented sinks. -/// -/// Implementors of the `Write` trait are sometimes called 'writers'. -/// -/// Writers are defined by two required methods, [`write`] and [`flush`]: -/// -/// * The [`write`] method will attempt to write some data into the object, -/// returning how many bytes were successfully written. -/// -/// * The [`flush`] method is useful for adapters and explicit buffers -/// themselves for ensuring that all buffered data has been pushed out to the -/// 'true sink'. -/// -/// Writers are intended to be composable with one another. Many implementors -/// throughout [`std::io`] take and provide types which implement the `Write` -/// trait. -/// -/// [`write`]: Write::write -/// [`flush`]: Write::flush -/// [`std::io`]: self -/// -/// # Examples -/// -/// ```no_run -/// use std::io::prelude::*; -/// use std::fs::File; -/// -/// fn main() -> std::io::Result<()> { -/// let data = b"some bytes"; -/// -/// let mut pos = 0; -/// let mut buffer = File::create("foo.txt")?; -/// -/// while pos < data.len() { -/// let bytes_written = buffer.write(&data[pos..])?; -/// pos += bytes_written; -/// } -/// Ok(()) -/// } -/// ``` -/// -/// The trait also provides convenience methods like [`write_all`], which calls -/// `write` in a loop until its entire input has been written. -/// -/// [`write_all`]: Write::write_all -#[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait)] -#[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] -pub trait Write { - /// Writes a buffer into this writer, returning how many bytes were written. - /// - /// This function will attempt to write the entire contents of `buf`, but - /// the entire write might not succeed, or the write may also generate an - /// error. Typically, a call to `write` represents one attempt to write to - /// any wrapped object. - /// - /// Calls to `write` are not guaranteed to block waiting for data to be - /// written, and a write which would otherwise block can be indicated through - /// an [`Err`] variant. - /// - /// If this method consumed `n > 0` bytes of `buf` it must return [`Ok(n)`]. - /// If the return value is `Ok(n)` then `n` must satisfy `n <= buf.len()`. - /// A return value of `Ok(0)` typically means that the underlying object is - /// no longer able to accept bytes and will likely not be able to in the - /// future as well, or that the buffer provided is empty. - /// - /// # Errors - /// - /// Each call to `write` may generate an I/O error indicating that the - /// operation could not be completed. If an error is returned then no bytes - /// in the buffer were written to this writer. - /// - /// It is **not** considered an error if the entire buffer could not be - /// written to this writer. - /// - /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the - /// write operation should be retried if there is nothing else to do. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// // Writes some prefix of the byte string, not necessarily all of it. - /// buffer.write(b"some bytes")?; - /// Ok(()) - /// } - /// ``` - /// - /// [`Ok(n)`]: Ok - #[stable(feature = "rust1", since = "1.0.0")] - fn write(&mut self, buf: &[u8]) -> Result; - - /// Like [`write`], except that it writes from a slice of buffers. - /// - /// Data is copied from each buffer in order, with the final buffer - /// read from possibly being only partially consumed. This method must - /// behave as a call to [`write`] with the buffers concatenated would. - /// - /// The default implementation calls [`write`] with either the first nonempty - /// buffer provided, or an empty one if none exists. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::IoSlice; - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let data1 = [1; 8]; - /// let data2 = [15; 8]; - /// let io_slice1 = IoSlice::new(&data1); - /// let io_slice2 = IoSlice::new(&data2); - /// - /// let mut buffer = File::create("foo.txt")?; - /// - /// // Writes some prefix of the byte string, not necessarily all of it. - /// buffer.write_vectored(&[io_slice1, io_slice2])?; - /// Ok(()) - /// } - /// ``` - /// - /// [`write`]: Write::write - #[stable(feature = "iovec", since = "1.36.0")] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { - default_write_vectored(|b| self.write(b), bufs) - } - - /// Determines if this `Write`r has an efficient [`write_vectored`] - /// implementation. - /// - /// If a `Write`r does not override the default [`write_vectored`] - /// implementation, code using it may want to avoid the method all together - /// and coalesce writes into a single buffer for higher performance. - /// - /// The default implementation returns `false`. - /// - /// [`write_vectored`]: Write::write_vectored - #[unstable(feature = "can_vector", issue = "69941")] - fn is_write_vectored(&self) -> bool { - false - } - - /// Flushes this output stream, ensuring that all intermediately buffered - /// contents reach their destination. - /// - /// # Errors - /// - /// It is considered an error if not all bytes could be written due to - /// I/O errors or EOF being reached. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::io::BufWriter; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = BufWriter::new(File::create("foo.txt")?); - /// - /// buffer.write_all(b"some bytes")?; - /// buffer.flush()?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn flush(&mut self) -> Result<()>; - - /// Attempts to write an entire buffer into this writer. - /// - /// This method will continuously call [`write`] until there is no more data - /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is - /// returned. This method will not return until the entire buffer has been - /// successfully written or such an error occurs. The first error that is - /// not of [`ErrorKind::Interrupted`] kind generated from this method will be - /// returned. - /// - /// If the buffer contains no data, this will never call [`write`]. - /// - /// # Errors - /// - /// This function will return the first error of - /// non-[`ErrorKind::Interrupted`] kind that [`write`] returns. - /// - /// [`write`]: Write::write - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// buffer.write_all(b"some bytes")?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { - while !buf.is_empty() { - match self.write(buf) { - Ok(0) => { - return Err(Error::WRITE_ALL_EOF); - } - Ok(n) => buf = &buf[n..], - Err(ref e) if e.is_interrupted() => {} - Err(e) => return Err(e), - } - } - Ok(()) - } - - /// Attempts to write multiple buffers into this writer. - /// - /// This method will continuously call [`write_vectored`] until there is no - /// more data to be written or an error of non-[`ErrorKind::Interrupted`] - /// kind is returned. This method will not return until all buffers have - /// been successfully written or such an error occurs. The first error that - /// is not of [`ErrorKind::Interrupted`] kind generated from this method - /// will be returned. - /// - /// If the buffer contains no data, this will never call [`write_vectored`]. - /// - /// # Notes - /// - /// Unlike [`write_vectored`], this takes a *mutable* reference to - /// a slice of [`IoSlice`]s, not an immutable one. That's because we need to - /// modify the slice to keep track of the bytes already written. - /// - /// Once this function returns, the contents of `bufs` are unspecified, as - /// this depends on how many calls to [`write_vectored`] were necessary. It is - /// best to understand this function as taking ownership of `bufs` and to - /// not use `bufs` afterwards. The underlying buffers, to which the - /// [`IoSlice`]s point (but not the [`IoSlice`]s themselves), are unchanged and - /// can be reused. - /// - /// [`write_vectored`]: Write::write_vectored - /// - /// # Examples - /// - /// ``` - /// #![feature(write_all_vectored)] - /// # fn main() -> std::io::Result<()> { - /// - /// use std::io::{Write, IoSlice}; - /// - /// let mut writer = Vec::new(); - /// let bufs = &mut [ - /// IoSlice::new(&[1]), - /// IoSlice::new(&[2, 3]), - /// IoSlice::new(&[4, 5, 6]), - /// ]; - /// - /// writer.write_all_vectored(bufs)?; - /// // Note: the contents of `bufs` is now undefined, see the Notes section. - /// - /// assert_eq!(writer, &[1, 2, 3, 4, 5, 6]); - /// # Ok(()) } - /// ``` - #[unstable(feature = "write_all_vectored", issue = "70436")] - fn write_all_vectored(&mut self, mut bufs: &mut [IoSlice<'_>]) -> Result<()> { - // Guarantee that bufs is empty if it contains no data, - // to avoid calling write_vectored if there is no data to be written. - IoSlice::advance_slices(&mut bufs, 0); - while !bufs.is_empty() { - match self.write_vectored(bufs) { - Ok(0) => { - return Err(Error::WRITE_ALL_EOF); - } - Ok(n) => IoSlice::advance_slices(&mut bufs, n), - Err(ref e) if e.is_interrupted() => {} - Err(e) => return Err(e), - } - } - Ok(()) - } - - /// Writes a formatted string into this writer, returning any error - /// encountered. - /// - /// This method is primarily used to interface with the - /// [`format_args!()`] macro, and it is rare that this should - /// explicitly be called. The [`write!()`] macro should be favored to - /// invoke this method instead. - /// - /// This function internally uses the [`write_all`] method on - /// this trait and hence will continuously write data so long as no errors - /// are received. This also means that partial writes are not indicated in - /// this signature. - /// - /// [`write_all`]: Write::write_all - /// - /// # Errors - /// - /// This function will return any I/O error reported while formatting. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// // this call - /// write!(buffer, "{:.*}", 2, 1.234567)?; - /// // turns into this: - /// buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> { - if let Some(s) = args.as_statically_known_str() { - self.write_all(s.as_bytes()) - } else { - default_write_fmt(self, args) - } - } - - /// Creates a "by reference" adapter for this instance of `Write`. - /// - /// The returned adapter also implements `Write` and will simply borrow this - /// current writer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::Write; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// let reference = buffer.by_ref(); - /// - /// // we can use reference just like our original buffer - /// reference.write_all(b"some bytes")?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn by_ref(&mut self) -> &mut Self - where - Self: Sized, - { - self - } -} - fn read_until(r: &mut R, delim: u8, buf: &mut Vec) -> Result { let mut read = 0; loop { diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs index ce2137f9567c7..ac3e0f84d1e80 100644 --- a/library/std/src/io/util.rs +++ b/library/std/src/io/util.rs @@ -3,10 +3,7 @@ #[cfg(test)] mod tests; -use crate::fmt; -use crate::io::{ - self, BorrowedCursor, BufRead, Empty, IoSlice, IoSliceMut, Read, Repeat, Sink, Write, -}; +use crate::io::{self, BorrowedCursor, BufRead, Empty, IoSliceMut, Read, Repeat}; #[stable(feature = "rust1", since = "1.0.0")] impl Read for Empty { @@ -83,84 +80,6 @@ impl BufRead for Empty { } } -#[stable(feature = "empty_write", since = "1.73.0")] -impl Write for Empty { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "empty_write", since = "1.73.0")] -impl Write for &Empty { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - #[stable(feature = "rust1", since = "1.0.0")] impl Read for Repeat { #[inline] @@ -213,81 +132,3 @@ impl Read for Repeat { true } } - -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Sink { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "write_mt", since = "1.48.0")] -impl Write for &Sink { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index db37df63c5763..7cf576ecd8803 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -322,6 +322,7 @@ #![feature(borrowed_buf_init)] #![feature(bstr)] #![feature(bstr_internals)] +#![feature(can_vector)] #![feature(cast_maybe_uninit)] #![feature(char_internals)] #![feature(clone_to_uninit)] @@ -389,6 +390,7 @@ #![feature(ub_checks)] #![feature(uint_carryless_mul)] #![feature(used_with_arg)] +#![feature(write_all_vectored)] // tidy-alphabetical-end // // Library features (alloc): diff --git a/tests/ui/suggestions/issue-105645.stderr b/tests/ui/suggestions/issue-105645.stderr index 2b9a94d8e0ab2..2342c22cfa80a 100644 --- a/tests/ui/suggestions/issue-105645.stderr +++ b/tests/ui/suggestions/issue-105645.stderr @@ -7,7 +7,7 @@ LL | foo(&mut bref); | required by a bound introduced by this call | help: the trait `std::io::Write` is implemented for `&mut [u8]` - --> $SRC_DIR/std/src/io/impls.rs:LL:COL + --> $SRC_DIR/core/src/io/impls.rs:LL:COL note: required by a bound in `foo` --> $DIR/issue-105645.rs:8:21 | From 4ecb0cd6a621e90ecd6bd2b0054091b7be853de8 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 22 May 2026 10:28:42 +1000 Subject: [PATCH 48/78] Fix documentation links to `Write` --- library/core/src/io/cursor.rs | 2 +- library/core/src/io/error.rs | 4 ++-- library/core/src/io/mod.rs | 2 +- library/core/src/io/util.rs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index 15b313142c963..645e69a4278e5 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -23,7 +23,7 @@ use crate::io::{self, ErrorKind, IoSlice, SeekFrom, Write}; /// [bytes]: crate::slice "slice" /// [`File`]: ../../std/fs/struct.File.html /// [`Read`]: ../../std/io/trait.Read.html -/// [`Write`]: ../../std/io/trait.Write.html +/// [`Write`]: crate::io::Write /// [`Seek`]: crate::io::Seek /// [Vec]: ../../alloc/vec/struct.Vec.html /// diff --git a/library/core/src/io/error.rs b/library/core/src/io/error.rs index 4214a5f87e3ef..eac7396bcfd83 100644 --- a/library/core/src/io/error.rs +++ b/library/core/src/io/error.rs @@ -79,7 +79,7 @@ pub type Result = result::Result; /// // FIXME(#74481): Hard-links required to link from `core` to `std` /// [Read]: ../../std/io/trait.Read.html -/// [Write]: ../../std/io/trait.Write.html +/// [Write]: crate::io::Write /// [Seek]: crate::io::Seek #[stable(feature = "rust1", since = "1.0.0")] #[rustc_has_incoherent_inherent_impls] @@ -853,7 +853,7 @@ pub enum ErrorKind { /// particular number of bytes but only a smaller number of bytes could be /// written. /// - /// [write]: ../../std/io/trait.Write.html#tymethod.write + /// [write]: crate::io::Write::write /// [`Ok(0)`]: Ok #[stable(feature = "rust1", since = "1.0.0")] WriteZero, diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 98680ad390afc..0879d14d7ee04 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -55,7 +55,7 @@ pub use self::{ // FIXME(#74481): Hard-links required to link from `core` to `std` /// [file]: ../../std/fs/struct.File.html /// [arc]: ../../alloc/sync/struct.Arc.html -/// [`Write`]: ../../std/io/trait.Write.html +/// [`Write`]: crate::io::Write /// [`Seek`]: crate::io::Seek #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] diff --git a/library/core/src/io/util.rs b/library/core/src/io/util.rs index 4ffd38a906f1c..b022d876eb8f5 100644 --- a/library/core/src/io/util.rs +++ b/library/core/src/io/util.rs @@ -4,7 +4,7 @@ use crate::{cmp, fmt}; /// `Empty` ignores any data written via [`Write`], and will always be empty /// (returning zero bytes) when read via [`Read`]. /// -/// [`Write`]: ../../std/io/trait.Write.html +/// [`Write`]: crate::io::Write /// [`Read`]: ../../std/io/trait.Read.html /// /// This struct is generally created by calling [`empty()`]. Please @@ -129,7 +129,7 @@ impl Seek for Empty { /// [`Ok(buf.len())`]: Ok /// [`Ok(0)`]: Ok /// -/// [`write`]: ../../std/io/trait.Write.html#tymethod.write +/// [`write`]: crate::io::Write::write /// [`read`]: ../../std/io/trait.Read.html#tymethod.read /// /// # Examples @@ -303,7 +303,7 @@ impl Write for &Sink { /// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] /// and the contents of the buffer will not be inspected. /// -/// [`write`]: ../../std/io/trait.Write.html#tymethod.write +/// [`write`]: crate::io::Write::write /// [`Ok(buf.len())`]: Ok /// /// # Examples From 53727e6e468fb9a0fd8ab30f1f176605e8f5cc4d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 7 Jul 2026 11:23:55 +1000 Subject: [PATCH 49/78] Add documentation to `core::io` internal methods Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/core/src/io/cursor.rs | 9 ++++++++- library/core/src/io/write.rs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index 645e69a4278e5..fe4def531a84a 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -295,7 +295,8 @@ where } } -// Non-resizing write implementation +/// Non-resizing [`Write::write`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -308,6 +309,8 @@ pub fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Resul Ok(amt) } +/// Non-resizing [`Write::write_vectored`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -327,6 +330,8 @@ pub fn slice_write_vectored( Ok(nwritten) } +/// Non-resizing [`Write::write_all`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -335,6 +340,8 @@ pub fn slice_write_all(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::R if n < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } } +/// Non-resizing [`Write::write_all_vectored`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index 91d3d2c2f6787..90cfb94582d7f 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -366,6 +366,8 @@ pub trait Write { } } +/// Default implementation of [`Write::write_vectored`], which is currently used +/// in `libstd` for file system implementations of similar methods. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub fn default_write_vectored(write: F, bufs: &[IoSlice<'_>]) -> Result From 0e6181228a88e7e0f37591aca3cb64a8b0fdecf9 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 7 Jul 2026 12:12:19 +1000 Subject: [PATCH 50/78] Reduce visibility of `core::io::write::default_write_fmt` Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/mod.rs | 6 +++--- library/core/src/io/mod.rs | 2 +- library/core/src/io/write.rs | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 678934b91da71..ef7e95ed5bd7c 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -20,7 +20,7 @@ pub use core::io::{ #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use core::io::{ - IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_fmt, - default_write_vectored, slice_write, slice_write_all, slice_write_all_vectored, - slice_write_vectored, stream_len_default, take, + IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_vectored, + slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, + stream_len_default, take, }; diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 0879d14d7ee04..0134540ae86c8 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -38,7 +38,7 @@ pub use self::{ seek::stream_len_default, size_hint::SizeHint, util::{chain, take}, - write::{default_write_fmt, default_write_vectored}, + write::default_write_vectored, }; /// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index 90cfb94582d7f..cdddd380885f4 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -378,9 +378,7 @@ where write(buf) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn default_write_fmt(this: &mut W, args: fmt::Arguments<'_>) -> Result<()> { +fn default_write_fmt(this: &mut W, args: fmt::Arguments<'_>) -> Result<()> { // Create a shim which translates a `Write` to a `fmt::Write` and saves off // I/O errors, instead of discarding them. struct Adapter<'a, T: ?Sized + 'a> { From cb09bff72240065593630cac6c54aa15e691f26f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 7 Jul 2026 13:52:32 +1000 Subject: [PATCH 51/78] Add `Vec::try_extend_from_slice_of_bytes` Moves unsafe code from `alloc::io` into `alloc::vec`. A more general `try_extend_from_slice` may be useful in the future, but that requires changing how specialization is done for `SpecExtend` and that's a lot for an internal-only function. --- library/alloc/src/io/impls.rs | 15 +-------------- library/alloc/src/vec/mod.rs | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/library/alloc/src/io/impls.rs b/library/alloc/src/io/impls.rs index 0f6bfe244dba2..1351d3e3ef3a1 100644 --- a/library/alloc/src/io/impls.rs +++ b/library/alloc/src/io/impls.rs @@ -129,20 +129,7 @@ impl Write for Vec { fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { cfg_select! { no_global_oom_handling => { - let n = buf.len(); - self.try_reserve(n)?; - // SAFETY: - // * self and buf are non-overlapping - // * self[..len] is already initialized - // * self[len..len + n] is initialized by copy_nonoverlapping - // * len + n is within the capacity of self based on the reservation completed above - unsafe { - let len = self.len(); - let src = buf.as_ptr(); - let dst = self.as_mut_ptr().add(len); - core::ptr::copy_nonoverlapping(src, dst, n); - self.set_len(len + n); - } + self.try_extend_from_slice_of_bytes(buf)?; } _ => { self.extend_from_slice(buf); diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index c3b86a635bd0f..a5d03ef76ade6 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2938,8 +2938,26 @@ impl Vec { #[cfg(not(no_global_oom_handling))] #[inline] unsafe fn append_elements(&mut self, other: *const [T]) { + self.reserve(other.len()); + unsafe { + self.append_elements_unreserved(other); + } + } + + /// Appends elements to `self` from other buffer, returning [`TryReserveError`] on OOM. + #[inline] + unsafe fn try_append_elements(&mut self, other: *const [T]) -> Result<(), TryReserveError> { + self.try_reserve(other.len())?; + unsafe { + self.append_elements_unreserved(other); + } + Ok(()) + } + + /// Appends elements to `self` from other buffer without reserving additional capacity. + #[inline] + unsafe fn append_elements_unreserved(&mut self, other: *const [T]) { let count = other.len(); - self.reserve(count); let len = self.len(); if count > 0 { unsafe { @@ -3609,6 +3627,22 @@ impl Vec { } } +impl Vec { + #[cfg_attr( + not(no_global_oom_handling), + expect( + dead_code, + reason = "currently only used in IO module when global OOM handling is disabled" + ) + )] + pub(crate) fn try_extend_from_slice_of_bytes( + &mut self, + other: &[u8], + ) -> Result<(), TryReserveError> { + unsafe { self.try_append_elements(other) } + } +} + impl Vec<[T; N], A> { /// Takes a `Vec<[T; N]>` and flattens it into a `Vec`. /// From 33788973ce2455924e0371c710ac08de154ca709 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Wed, 8 Jul 2026 08:20:29 -0400 Subject: [PATCH 52/78] address suggestions --- compiler/rustc_codegen_ssa/src/back/link.rs | 41 ++++++++------------- compiler/rustc_codegen_ssa/src/base.rs | 36 +++++++++++------- compiler/rustc_codegen_ssa/src/lib.rs | 2 +- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 74631030e9b09..7ca6479548b55 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -94,40 +94,31 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat // earlier EII pass still handles missing impls and duplicate explicit impls. for dependency_formats in crate_info.dependency_formats.values() { for (eii_index, eii) in crate_info.eii_linkage.iter().enumerate() { - let mut explicit_impls = - eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); - let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { + let Some(explicit_impl) = eii.impls.first() else { continue; }; // If the explicit impl is already coming from a dylib, that dylib // has already resolved the default-vs-explicit choice. - if explicit_impls.next().is_some() - || matches!( - dependency_formats.get(explicit_impl.impl_crate), - Some(Linkage::Dynamic | Linkage::IncludedFromDylib) - ) - { + if matches!( + dependency_formats.get(explicit_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) { continue; } - let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| { - imp.is_default - && matches!( - dependency_formats.get(imp.impl_crate), - Some(Linkage::Dynamic | Linkage::IncludedFromDylib) - ) - }); - let Some((default_index, default_impl)) = finalized_default_impls.next() else { + let Some(default_impl) = &eii.default_impl else { continue; }; - - if !emitted.insert((eii_index, explicit_index, default_index)) { + if !matches!( + dependency_formats.get(default_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) { continue; } - let additional_crate_names = finalized_default_impls - .map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate))) - .collect::>(); + if !emitted.insert(eii_index) { + continue; + } sess.dcx().emit_err(DuplicateEiiImpls { name: eii.name, @@ -136,9 +127,9 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat second_span: default_impl.span, second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate), help: (), - additional_crates: (!additional_crate_names.is_empty()).then_some(()), - num_additional_crates: additional_crate_names.len(), - additional_crate_names: additional_crate_names.join(", "), + additional_crates: None, + num_additional_crates: 0, + additional_crate_names: String::new(), }); } } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index e35c446d609fc..213d943cb1a7d 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -933,21 +933,31 @@ fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { eiis.into_iter() .filter_map(|(_, FoundEii { decl, impls })| { - let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count(); - let has_default_impl = impls.values().any(|imp| imp.imp.is_default); + let mut explicit_impls = Vec::new(); + let mut default_impl = None; + + for (impl_did, FoundImpl { imp, impl_crate }) in impls { + let impl_info = EiiLinkageImplInfo { span: tcx.def_span(impl_did), impl_crate }; + if imp.is_default { + default_impl = Some(impl_info); + } else { + explicit_impls.push(impl_info); + } + } + // Only this case needs the link-time check. Missing impls and // duplicate explicit impls are handled in `rustc_passes`. - (explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo { - name: decl.name.name, - impls: impls - .into_iter() - .map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo { - span: tcx.def_span(impl_did), - impl_crate, - is_default: imp.is_default, - }) - .collect(), - }) + if explicit_impls.len() == 1 + && let Some(default_impl) = default_impl + { + Some(EiiLinkageInfo { + name: decl.name.name, + impls: explicit_impls, + default_impl: Some(default_impl), + }) + } else { + None + } }) .collect() } diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index ee8542e4c3c33..fc7627d7c0d5b 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -259,13 +259,13 @@ impl SymbolExport { pub struct EiiLinkageImplInfo { pub span: Span, pub impl_crate: CrateNum, - pub is_default: bool, } #[derive(Clone, Debug, Encodable, Decodable)] pub struct EiiLinkageInfo { pub name: Symbol, pub impls: Vec, + pub default_impl: Option, } /// Misc info we load from metadata to persist beyond the tcx. From f55de235dbbefa32f351aa110e6d9385d152abf9 Mon Sep 17 00:00:00 2001 From: Xuyang Zhang Date: Wed, 8 Jul 2026 21:41:51 +0800 Subject: [PATCH 53/78] Fix bootstrap submodule path prefix matching --- src/bootstrap/src/utils/helpers.rs | 6 +++++- src/bootstrap/src/utils/helpers/tests.rs | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index db740d8dcf624..b665891c9a206 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -96,7 +96,11 @@ pub fn is_dylib(path: &Path) -> bool { /// Return the path to the containing submodule if available. pub fn submodule_path_of(builder: &Builder<'_>, path: &str) -> Option { - let submodule_paths = builder.submodule_paths(); + submodule_path_of_paths(builder.submodule_paths(), path) +} + +fn submodule_path_of_paths(submodule_paths: &[String], path: &str) -> Option { + let path = Path::new(path); submodule_paths.iter().find_map(|submodule_path| { if path.starts_with(submodule_path) { Some(submodule_path.to_string()) } else { None } }) diff --git a/src/bootstrap/src/utils/helpers/tests.rs b/src/bootstrap/src/utils/helpers/tests.rs index 676fe6cbd5fe5..b051a374a0866 100644 --- a/src/bootstrap/src/utils/helpers/tests.rs +++ b/src/bootstrap/src/utils/helpers/tests.rs @@ -3,7 +3,7 @@ use std::io::Write; use std::path::PathBuf; use crate::utils::helpers::{ - check_cfg_arg, extract_beta_rev, hex_encode, make, set_file_times, submodule_path_of, + check_cfg_arg, extract_beta_rev, hex_encode, make, set_file_times, submodule_path_of_paths, symlink_dir, }; use crate::utils::tests::TestCtx; @@ -101,19 +101,22 @@ fn test_set_file_times_sanity_check() { #[test] fn test_submodule_path_of() { - let config = TestCtx::new().config("build").create_config(); + let submodules = vec!["src/tools/cargo".to_string(), "src/llvm-project".to_string()]; - let build = crate::Build::new(config.clone()); - let builder = crate::core::builder::Builder::new(&build); - assert_eq!(submodule_path_of(&builder, "invalid/path"), None); - assert_eq!(submodule_path_of(&builder, "src/tools/cargo"), Some("src/tools/cargo".to_string())); + assert_eq!(submodule_path_of_paths(&submodules, "invalid/path"), None); assert_eq!( - submodule_path_of(&builder, "src/llvm-project"), + submodule_path_of_paths(&submodules, "src/tools/cargo"), + Some("src/tools/cargo".to_string()) + ); + assert_eq!( + submodule_path_of_paths(&submodules, "src/llvm-project"), Some("src/llvm-project".to_string()) ); // Make sure subdirs are handled properly assert_eq!( - submodule_path_of(&builder, "src/tools/cargo/random-subdir"), + submodule_path_of_paths(&submodules, "src/tools/cargo/random-subdir"), Some("src/tools/cargo".to_string()) ); + // Make sure paths that only share a string prefix with a submodule are not matched. + assert_eq!(submodule_path_of_paths(&submodules, "src/tools/cargo-vendor"), None); } From 41bdaed53bc09ef3b9340af3dde256f8f06a99bc Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:35:19 -0400 Subject: [PATCH 54/78] address follow up nits --- compiler/rustc_codegen_ssa/src/base.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 213d943cb1a7d..c71def400f730 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -919,14 +919,14 @@ fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { let mut eiis = FxIndexMap::::default(); for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { - for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) { - eiis.entry(*did) - .or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() }) + for (&did, &(decl, ref impls)) in tcx.externally_implementable_items(cnum) { + eiis.entry(did) + .or_insert_with(|| FoundEii { decl, impls: Default::default() }) .impls .extend( impls .into_iter() - .map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })), + .map(|(&did, &imp)| (did, FoundImpl { imp, impl_crate: cnum })), ); } } @@ -945,11 +945,9 @@ fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { } } - // Only this case needs the link-time check. Missing impls and - // duplicate explicit impls are handled in `rustc_passes`. - if explicit_impls.len() == 1 - && let Some(default_impl) = default_impl - { + // Link time check is only needed when there may be a default impl in a dylib. + // Other cases emit an error in `rustc_passes` already. + if let Some(default_impl) = default_impl { Some(EiiLinkageInfo { name: decl.name.name, impls: explicit_impls, From b8d3a4ac9cfeb81d1e929fe6eab0af4b77ba3bb4 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Wed, 8 Jul 2026 11:48:53 -0400 Subject: [PATCH 55/78] Reapply "LLVM 23: Run AssignGUIDPass in some places" This reverts commit 4919940fad92a2a503e3fe7fc6a5594fb9771e72. --- compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp | 9 +++++++++ tests/codegen-llvm/bpf-allows-unaligned.rs | 2 +- tests/codegen-llvm/branch-protection.rs | 2 +- tests/codegen-llvm/frame-pointer-cli-control.rs | 2 +- tests/codegen-llvm/frame-pointer.rs | 2 +- tests/codegen-llvm/gpu-convergent.rs | 2 +- tests/codegen-llvm/instrument-coverage/testprog.rs | 4 ++-- tests/codegen-llvm/link_section.rs | 2 +- ...pe-metadata-itanium-cxx-abi-normalized-generalized.rs | 6 +++--- .../some-non-zero-from-atomic-optimization.rs | 2 +- .../codegen-llvm/target-feature-negative-implication.rs | 2 +- tests/codegen-llvm/target-feature-overrides.rs | 4 ++-- tests/codegen-llvm/unwind-abis/aapcs-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/c-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/cdecl-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/fastcall-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/stdcall-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/system-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/sysv64-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/thiscall-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/vectorcall-unwind-abi.rs | 4 ++-- tests/codegen-llvm/unwind-abis/win64-unwind-abi.rs | 4 ++-- 22 files changed, 44 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index fe5b8edce4a1d..e4807f10805c9 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -49,6 +49,9 @@ #include "llvm/Transforms/Instrumentation/RealtimeSanitizer.h" #include "llvm/Transforms/Instrumentation/ThreadSanitizer.h" #include "llvm/Transforms/Scalar/AnnotationRemarks.h" +#if LLVM_VERSION_GE(23, 0) +#include "llvm/Transforms/Utils/AssignGUID.h" +#endif #include "llvm/Transforms/Utils/CanonicalizeAliases.h" #include "llvm/Transforms/Utils/FunctionImportUtils.h" #include "llvm/Transforms/Utils/NameAnonGlobals.h" @@ -934,6 +937,9 @@ extern "C" LLVMRustResult LLVMRustOptimize( if (NeedThinLTOBufferPasses) { MPM.addPass(CanonicalizeAliasesPass()); MPM.addPass(NameAnonGlobalPass()); +#if LLVM_VERSION_GE(23, 0) + MPM.addPass(AssignGUIDPass()); +#endif } // For `-Copt-level=0`, and the pre-link fat/thin LTO stages. if (ThinLTOBufferRef && *ThinLTOBufferRef == nullptr) { @@ -1475,6 +1481,9 @@ extern "C" LLVMRustBuffer *LLVMRustModuleSerialize(LLVMModuleRef M, PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); ModulePassManager MPM; +#if LLVM_VERSION_GE(23, 0) + MPM.addPass(AssignGUIDPass()); +#endif MPM.addPass(ThinLTOBitcodeWriterPass(OS, nullptr)); MPM.run(*unwrap(M), MAM); } else { diff --git a/tests/codegen-llvm/bpf-allows-unaligned.rs b/tests/codegen-llvm/bpf-allows-unaligned.rs index c7a70d5b2e502..7e95a56d984c9 100644 --- a/tests/codegen-llvm/bpf-allows-unaligned.rs +++ b/tests/codegen-llvm/bpf-allows-unaligned.rs @@ -5,7 +5,7 @@ #[no_mangle] #[target_feature(enable = "allows-misaligned-mem-access")] -// CHECK: define noundef zeroext i8 @foo(i8 noundef returned %arg) unnamed_addr #0 { +// CHECK: define noundef zeroext i8 @foo(i8 noundef returned %arg) unnamed_addr #0 pub unsafe fn foo(arg: u8) -> u8 { arg } diff --git a/tests/codegen-llvm/branch-protection.rs b/tests/codegen-llvm/branch-protection.rs index ed1cb2cd137ea..11847c256d6ba 100644 --- a/tests/codegen-llvm/branch-protection.rs +++ b/tests/codegen-llvm/branch-protection.rs @@ -22,7 +22,7 @@ extern crate minicore; use minicore::*; // A basic test function. -// CHECK: @test(){{.*}} [[ATTR:#[0-9]+]] { +// CHECK: @test(){{.*}} [[ATTR:#[0-9]+]] #[no_mangle] pub fn test() {} diff --git a/tests/codegen-llvm/frame-pointer-cli-control.rs b/tests/codegen-llvm/frame-pointer-cli-control.rs index 911a5f03cbcda..79cdfc70f1ad7 100644 --- a/tests/codegen-llvm/frame-pointer-cli-control.rs +++ b/tests/codegen-llvm/frame-pointer-cli-control.rs @@ -45,7 +45,7 @@ Specific cases where platforms or tools rely on frame pointers for sound or corr extern crate minicore; -// CHECK: i32 @peach{{.*}}[[PEACH_ATTRS:\#[0-9]+]] { +// CHECK: i32 @peach{{.*}}[[PEACH_ATTRS:\#[0-9]+]] #[no_mangle] pub fn peach(x: u32) -> u32 { x diff --git a/tests/codegen-llvm/frame-pointer.rs b/tests/codegen-llvm/frame-pointer.rs index 1d0dd762826b2..a52d95a23862d 100644 --- a/tests/codegen-llvm/frame-pointer.rs +++ b/tests/codegen-llvm/frame-pointer.rs @@ -18,7 +18,7 @@ extern crate minicore; use minicore::*; -// CHECK: define i32 @peach{{.*}}[[PEACH_ATTRS:\#[0-9]+]] { +// CHECK: define i32 @peach{{.*}}[[PEACH_ATTRS:\#[0-9]+]] #[no_mangle] pub fn peach(x: u32) -> u32 { x diff --git a/tests/codegen-llvm/gpu-convergent.rs b/tests/codegen-llvm/gpu-convergent.rs index bb9271ab69996..376d65a3d4a25 100644 --- a/tests/codegen-llvm/gpu-convergent.rs +++ b/tests/codegen-llvm/gpu-convergent.rs @@ -17,7 +17,7 @@ extern "C" { fn ext(); } -// CHECK: define {{.*}}_kernel void @fun(i32{{.*}}) unnamed_addr #[[ATTR:[0-9]+]] { +// CHECK: define {{.*}}_kernel void @fun(i32{{.*}}) unnamed_addr #[[ATTR:[0-9]+]] // CHECK: declare void @ext() unnamed_addr #[[ATTR]] // CHECK: attributes #[[ATTR]] = {{.*}} convergent #[no_mangle] diff --git a/tests/codegen-llvm/instrument-coverage/testprog.rs b/tests/codegen-llvm/instrument-coverage/testprog.rs index ef61ede6de8ee..67c49c438f9f7 100644 --- a/tests/codegen-llvm/instrument-coverage/testprog.rs +++ b/tests/codegen-llvm/instrument-coverage/testprog.rs @@ -101,7 +101,7 @@ fn main() { // CHECK-SAME: @__llvm_prf_nm // CHECK-SAME: section "llvm.metadata" -// CHECK: define internal { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} { +// CHECK: define internal { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} // CHECK-NEXT: start: // CHECK-NOT: define internal // CHECK: atomicrmw add ptr @@ -109,7 +109,7 @@ fn main() { // CHECK: declare void @llvm.instrprof.increment(ptr, i64, i32, i32) #[[LLVM_INSTRPROF_INCREMENT_ATTR:[0-9]+]] -// WIN: define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #[[LLVM_PROFILE_RUNTIME_USER_ATTR:[0-9]+]] comdat {{.*}}{ +// WIN: define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #[[LLVM_PROFILE_RUNTIME_USER_ATTR:[0-9]+]] comdat {{.*}} // WIN-NEXT: %1 = load i32, ptr @__llvm_profile_runtime // WIN-NEXT: ret i32 %1 // WIN-NEXT: } diff --git a/tests/codegen-llvm/link_section.rs b/tests/codegen-llvm/link_section.rs index f196ea86c447d..61bde683c0a41 100644 --- a/tests/codegen-llvm/link_section.rs +++ b/tests/codegen-llvm/link_section.rs @@ -29,7 +29,7 @@ pub static VAR2: E = E::A(666); #[link_section = "__TEST,three"] pub static VAR3: E = E::B(1.); -// CHECK: define {{(dso_local )?}}void @fn1() {{.*}} section "__TEST,four" { +// CHECK: define {{(dso_local )?}}void @fn1() {{.*}} section "__TEST,four" #[no_mangle] #[link_section = "__TEST,four"] pub fn fn1() {} diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-itanium-cxx-abi-normalized-generalized.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-itanium-cxx-abi-normalized-generalized.rs index 7639ce7b10448..5b1aa97ab3338 100644 --- a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-itanium-cxx-abi-normalized-generalized.rs +++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-itanium-cxx-abi-normalized-generalized.rs @@ -7,21 +7,21 @@ pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { // CHECK-LABEL: define{{.*}}foo - // CHECK-SAME: {{.*}}![[TYPE1:[0-9]+]] + // CHECK-SAME: {{.*}}!type ![[TYPE1:[0-9]+]] // CHECK: call i1 @llvm.type.test(ptr {{%f|%0}}, metadata !"_ZTSFu3i32S_E.normalized.generalized") f(arg) } pub fn bar(f: fn(i32, i32) -> i32, arg1: i32, arg2: i32) -> i32 { // CHECK-LABEL: define{{.*}}bar - // CHECK-SAME: {{.*}}![[TYPE2:[0-9]+]] + // CHECK-SAME: {{.*}}!type ![[TYPE2:[0-9]+]] // CHECK: call i1 @llvm.type.test(ptr {{%f|%0}}, metadata !"_ZTSFu3i32S_S_E.normalized.generalized") f(arg1, arg2) } pub fn baz(f: fn(i32, i32, i32) -> i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { // CHECK-LABEL: define{{.*}}baz - // CHECK-SAME: {{.*}}![[TYPE3:[0-9]+]] + // CHECK-SAME: {{.*}}!type ![[TYPE3:[0-9]+]] // CHECK: call i1 @llvm.type.test(ptr {{%f|%0}}, metadata !"_ZTSFu3i32S_S_S_E.normalized.generalized") f(arg1, arg2, arg3) } diff --git a/tests/codegen-llvm/some-non-zero-from-atomic-optimization.rs b/tests/codegen-llvm/some-non-zero-from-atomic-optimization.rs index 35317b0dd39cc..3df2d569f9a40 100644 --- a/tests/codegen-llvm/some-non-zero-from-atomic-optimization.rs +++ b/tests/codegen-llvm/some-non-zero-from-atomic-optimization.rs @@ -72,7 +72,7 @@ pub unsafe fn some_non_zero_from_atomic_get() -> Option { /// /// The way we check that the LLVM IR is correct is by making sure that neither /// `panic` nor `unreachable` is part of the LLVM IR: -// CHECK-LABEL: define {{.*}} i64 @some_non_zero_from_atomic_get2() {{.*}} { +// CHECK-LABEL: define {{.*}} i64 @some_non_zero_from_atomic_get2() {{.*}} // CHECK-NOT: panic // CHECK-NOT: unreachable #[no_mangle] diff --git a/tests/codegen-llvm/target-feature-negative-implication.rs b/tests/codegen-llvm/target-feature-negative-implication.rs index a9cdca4283991..376599738e526 100644 --- a/tests/codegen-llvm/target-feature-negative-implication.rs +++ b/tests/codegen-llvm/target-feature-negative-implication.rs @@ -13,7 +13,7 @@ use minicore::*; #[no_mangle] pub unsafe fn banana() { // CHECK-LABEL: @banana() - // CHECK-SAME: [[BANANAATTRS:#[0-9]+]] { + // CHECK-SAME: [[BANANAATTRS:#[0-9]+]] } // CHECK: attributes [[BANANAATTRS]] diff --git a/tests/codegen-llvm/target-feature-overrides.rs b/tests/codegen-llvm/target-feature-overrides.rs index 2adc8ee6f53bc..3bf05c7977ebf 100644 --- a/tests/codegen-llvm/target-feature-overrides.rs +++ b/tests/codegen-llvm/target-feature-overrides.rs @@ -23,7 +23,7 @@ extern "C" { #[no_mangle] pub unsafe fn apple() -> u32 { // CHECK-LABEL: @apple() - // CHECK-SAME: [[APPLEATTRS:#[0-9]+]] { + // CHECK-SAME: [[APPLEATTRS:#[0-9]+]] // CHECK: {{.*}}call{{.*}}@peach peach() } @@ -32,7 +32,7 @@ pub unsafe fn apple() -> u32 { #[no_mangle] pub unsafe fn banana() -> u32 { // CHECK-LABEL: @banana() - // CHECK-SAME: [[BANANAATTRS:#[0-9]+]] { + // CHECK-SAME: [[BANANAATTRS:#[0-9]+]] // COMPAT: {{.*}}call{{.*}}@peach // INCOMPAT: {{.*}}call{{.*}}@apple apple() // Compatible for inline in COMPAT revision and can't be inlined in INCOMPAT diff --git a/tests/codegen-llvm/unwind-abis/aapcs-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/aapcs-unwind-abi.rs index ecace722e0dbe..279780e3a7aeb 100644 --- a/tests/codegen-llvm/unwind-abis/aapcs-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/aapcs-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // `aapcs-unwind` extern functions. `aapcs-unwind` functions MUST NOT have this attribute. We // disable optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "aapcs" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "aapcs-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/c-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/c-unwind-abi.rs index 46c08b5fc4ff4..1b3312839e3e8 100644 --- a/tests/codegen-llvm/unwind-abis/c-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/c-unwind-abi.rs @@ -7,11 +7,11 @@ #![crate_type = "lib"] -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "C" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "C-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/cdecl-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/cdecl-unwind-abi.rs index 8e643d6ce4947..6f4eafb353ccb 100644 --- a/tests/codegen-llvm/unwind-abis/cdecl-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/cdecl-unwind-abi.rs @@ -7,11 +7,11 @@ #![crate_type = "lib"] -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "cdecl" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "cdecl-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/fastcall-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/fastcall-unwind-abi.rs index 7df46813ed1dd..51c6fd15b9c5c 100644 --- a/tests/codegen-llvm/unwind-abis/fastcall-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/fastcall-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // `fastcall-unwind` extern functions. `fastcall-unwind` functions MUST NOT have this attribute. We // disable optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "fastcall" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "fastcall-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/stdcall-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/stdcall-unwind-abi.rs index cc06ee125495a..b5fcea52b4d61 100644 --- a/tests/codegen-llvm/unwind-abis/stdcall-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/stdcall-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // extern functions. `stdcall-unwind` functions MUST NOT have this attribute. We disable // optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "stdcall" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "stdcall-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/system-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/system-unwind-abi.rs index 5f9102483464b..15fce95fe285b 100644 --- a/tests/codegen-llvm/unwind-abis/system-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/system-unwind-abi.rs @@ -7,11 +7,11 @@ #![crate_type = "lib"] -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "system" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "system-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/sysv64-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/sysv64-unwind-abi.rs index 69bfaf80b4be6..1293e7c0a5f82 100644 --- a/tests/codegen-llvm/unwind-abis/sysv64-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/sysv64-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // `sysv64-unwind` extern functions. `sysv64-unwind` functions MUST NOT have this attribute. We // disable optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "sysv64" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "sysv64-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/thiscall-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/thiscall-unwind-abi.rs index 05f6b8b70e171..a9b6c34ee58e2 100644 --- a/tests/codegen-llvm/unwind-abis/thiscall-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/thiscall-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // `thiscall-unwind` extern functions. `thiscall-unwind` functions MUST NOT have this attribute. We // disable optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "thiscall" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "thiscall-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/vectorcall-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/vectorcall-unwind-abi.rs index d001a16b32a1c..8cedb55ae1d28 100644 --- a/tests/codegen-llvm/unwind-abis/vectorcall-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/vectorcall-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // `vectorcall-unwind` extern functions. `vectorcall-unwind` functions MUST NOT have this attribute. // We disable optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "vectorcall" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "vectorcall-unwind" fn rust_item_that_can_unwind() {} diff --git a/tests/codegen-llvm/unwind-abis/win64-unwind-abi.rs b/tests/codegen-llvm/unwind-abis/win64-unwind-abi.rs index 257f00b54e4d8..2a3ad330406e6 100644 --- a/tests/codegen-llvm/unwind-abis/win64-unwind-abi.rs +++ b/tests/codegen-llvm/unwind-abis/win64-unwind-abi.rs @@ -16,11 +16,11 @@ pub trait Sized: MetaSized {} // `win64-unwind` extern functions. `win64-unwind` functions MUST NOT have this attribute. We // disable optimizations above to prevent LLVM from inferring the attribute. -// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 { +// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 #[no_mangle] pub extern "win64" fn rust_item_that_cannot_unwind() {} -// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 { +// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 #[no_mangle] pub extern "win64-unwind" fn rust_item_that_can_unwind() {} From 90496ea27d58bb862ff719345120c38eeb3c789c Mon Sep 17 00:00:00 2001 From: Alejandra Gonzalez Date: Wed, 8 Jul 2026 18:40:44 +0200 Subject: [PATCH 56/78] fix: Actually take error and panic by default, only emitting a custom error if file ends in .rs --- compiler/rustc_driver_impl/src/lib.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 98663d3db1335..f315d03a00986 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -598,25 +598,28 @@ fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { match sess.io.input { Input::File(ref path) => { let mut v = Vec::new(); - if locator::list_file_metadata( + if let Err(error) = locator::list_file_metadata( &sess.target, path, metadata_loader, &mut v, &sess.opts.unstable_opts.ls, sess.cfg_version, - ) - .is_err() - && path.extension().is_some_and(|extension| extension == "rs") - { - let mut err = sess - .dcx() - .struct_fatal("`-Zls` takes a `.rmeta` file as input, not a source file"); - if rustc_session::utils::was_invoked_from_cargo() { - // Give a Cargo-tailored suggestion if we're coming from Cargo - err.note("use `rustc +nightly -Zls=... path/to/file.rmeta` directly, instead of going through Cargo"); + ) { + // If the panic comes from the input being the wrong file, direct the user to .rmeta files + if path.extension().is_some_and(|extension| extension == "rs") { + let mut err = sess + .dcx() + .struct_fatal("`-Zls` takes a `.rmeta` file as input, not a source file"); + if rustc_session::utils::was_invoked_from_cargo() { + // Give a Cargo-tailored suggestion if we're coming from Cargo + err.note("use `rustc +nightly -Zls=... path/to/file.rmeta` directly, instead of going through Cargo"); + } + err.emit(); } - err.emit(); + + // Else, just panic (This shouldn't be reached); + panic!("{error}"); } safe_println!("{}", String::from_utf8(v).unwrap()); } From a5ee71ca93f4175998e62b8a36d65bb219311bae Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 8 Jul 2026 21:19:49 +0200 Subject: [PATCH 57/78] use f32 intrinsic directly in fallback for f16 intrinsic --- library/core/src/intrinsics/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 84e8aa9569fec..a520fe5811e53 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -1088,7 +1088,7 @@ pub fn powif128(a: f128, x: i32) -> f128; #[rustc_intrinsic] #[rustc_nounwind] pub fn sinf16(x: f16) -> f16 { - libm::likely_available::sinf(x as f32) as f16 + sinf32(x as f32) as f16 } /// Returns the sine of an `f32`. /// @@ -1129,7 +1129,7 @@ pub fn sinf128(x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn cosf16(x: f16) -> f16 { - libm::likely_available::cosf(x as f32) as f16 + cosf32(x as f32) as f16 } /// Returns the cosine of an `f32`. /// @@ -1170,7 +1170,7 @@ pub fn cosf128(x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn powf16(a: f16, x: f16) -> f16 { - libm::likely_available::powf(a as f32, x as f32) as f16 + powf32(a as f32, x as f32) as f16 } /// Raises an `f32` to an `f32` power. /// @@ -1211,7 +1211,7 @@ pub fn powf128(a: f128, x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn expf16(x: f16) -> f16 { - libm::likely_available::expf(x as f32) as f16 + expf32(x as f32) as f16 } /// Returns the exponential of an `f32`. /// @@ -1252,7 +1252,7 @@ pub fn expf128(x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn exp2f16(x: f16) -> f16 { - libm::likely_available::exp2f(x as f32) as f16 + exp2f32(x as f32) as f16 } /// Returns 2 raised to the power of an `f32`. /// @@ -1293,7 +1293,7 @@ pub fn exp2f128(x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn logf16(x: f16) -> f16 { - libm::likely_available::logf(x as f32) as f16 + logf32(x as f32) as f16 } /// Returns the natural logarithm of an `f32`. /// @@ -1334,7 +1334,7 @@ pub fn logf128(x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn log10f16(x: f16) -> f16 { - libm::likely_available::log10f(x as f32) as f16 + log10f32(x as f32) as f16 } /// Returns the base 10 logarithm of an `f32`. /// @@ -1375,7 +1375,7 @@ pub fn log10f128(x: f128) -> f128 { #[rustc_intrinsic] #[rustc_nounwind] pub fn log2f16(x: f16) -> f16 { - libm::likely_available::log2f(x as f32) as f16 + log2f32(x as f32) as f16 } /// Returns the base 2 logarithm of an `f32`. /// From 58c2c076ca9cd8ffbdbe031ad829f96ddc32b048 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 8 Jul 2026 23:33:23 +0200 Subject: [PATCH 58/78] on msvc, make f32 fallback to f64 the 32-bit symbols are not available on msvc --- library/core/src/intrinsics/mod.rs | 40 ++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index a520fe5811e53..b812b9eb2afc1 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -1098,7 +1098,10 @@ pub fn sinf16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn sinf32(x: f32) -> f32 { - libm::likely_available::sinf(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => sinf64(x as f64) as f32, + _ => libm::likely_available::sinf(x), + } } /// Returns the sine of an `f64`. /// @@ -1139,7 +1142,10 @@ pub fn cosf16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn cosf32(x: f32) -> f32 { - libm::likely_available::cosf(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => cosf64(x as f64) as f32, + _ => libm::likely_available::cosf(x), + } } /// Returns the cosine of an `f64`. /// @@ -1180,7 +1186,10 @@ pub fn powf16(a: f16, x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn powf32(a: f32, x: f32) -> f32 { - libm::likely_available::powf(a, x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => powf64(a as f64, x as f64) as f32, + _ => libm::likely_available::powf(a, x), + } } /// Raises an `f64` to an `f64` power. /// @@ -1221,7 +1230,10 @@ pub fn expf16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn expf32(x: f32) -> f32 { - libm::likely_available::expf(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => expf64(x as f64) as f32, + _ => libm::likely_available::expf(x), + } } /// Returns the exponential of an `f64`. /// @@ -1262,7 +1274,10 @@ pub fn exp2f16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn exp2f32(x: f32) -> f32 { - libm::likely_available::exp2f(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => exp2f64(x as f64) as f32, + _ => libm::likely_available::exp2f(x), + } } /// Returns 2 raised to the power of an `f64`. /// @@ -1303,7 +1318,10 @@ pub fn logf16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn logf32(x: f32) -> f32 { - libm::likely_available::logf(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => logf64(x as f64) as f32, + _ => libm::likely_available::logf(x), + } } /// Returns the natural logarithm of an `f64`. /// @@ -1344,7 +1362,10 @@ pub fn log10f16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn log10f32(x: f32) -> f32 { - libm::likely_available::log10f(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => log10f64(x as f64) as f32, + _ => libm::likely_available::log10f(x), + } } /// Returns the base 10 logarithm of an `f64`. /// @@ -1385,7 +1406,10 @@ pub fn log2f16(x: f16) -> f16 { #[rustc_intrinsic] #[rustc_nounwind] pub fn log2f32(x: f32) -> f32 { - libm::likely_available::log2f(x) + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => log2f64(x as f64) as f32, + _ => libm::likely_available::log2f(x), + } } /// Returns the base 2 logarithm of an `f64`. /// From ed9bfaf27b067366f519432b59ad1a2d50d1c13e Mon Sep 17 00:00:00 2001 From: jackh726 Date: Thu, 9 Jul 2026 00:01:21 +0000 Subject: [PATCH 59/78] A couple minor things: rename args_known_to_outlive_associated_type_params, use tcx.assumed_wf_types, and test polonius_alpha in gat-outlives-implied test --- .../src/traits/outlives_for_liveness.rs | 8 +++----- .../gat-outlives-implied.edition2024.stderr | 2 +- .../gat-outlives-implied.polonius_alpha.stderr | 2 +- tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs | 1 + 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs index 191f75628a69e..5f8802310978e 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -136,9 +136,7 @@ pub(crate) fn args_known_to_outlive_alias_params<'tcx>( { args_known_to_outlive_opaque_params(tcx, opaque_def_id.expect_local()) } - DefKind::AssocTy | DefKind::TyAlias => { - args_known_to_outlive_associated_type_params(tcx, def_id) - } + DefKind::AssocTy | DefKind::TyAlias => args_known_to_outlive_non_opaque_params(tcx, def_id), kind => { bug!("improper def_kind {kind:?} passed to `live_args_for_alias_from_outlives_bounds`") } @@ -279,14 +277,14 @@ pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( } #[tracing::instrument(level = "debug", skip(tcx), ret)] -pub(crate) fn args_known_to_outlive_associated_type_params<'tcx>( +pub(crate) fn args_known_to_outlive_non_opaque_params<'tcx>( tcx: TyCtxt<'tcx>, def_id: LocalDefId, ) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); let param_env = tcx.param_env(def_id); tracing::debug!(?param_env); - let wf_tys = FxIndexSet::default(); + let wf_tys = tcx.assumed_wf_types(def_id).iter().map(|(ty, _)| *ty).collect::>(); let mut result = Vec::new(); for outlived_arg in self_identity_args.iter() { let Some(outlived_region) = outlived_arg.as_region() else { diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr index 9317846041925..2d733d142f0f6 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr @@ -1,5 +1,5 @@ error[E0506]: cannot assign to `cluster` because it is borrowed - --> $DIR/gat-outlives-implied.rs:19:9 + --> $DIR/gat-outlives-implied.rs:20:9 | LL | let changes = self.changes(&cluster); | -------- `cluster` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr index 9317846041925..2d733d142f0f6 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr @@ -1,5 +1,5 @@ error[E0506]: cannot assign to `cluster` because it is borrowed - --> $DIR/gat-outlives-implied.rs:19:9 + --> $DIR/gat-outlives-implied.rs:20:9 | LL | let changes = self.changes(&cluster); | -------- `cluster` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs index d5afb11742c35..223075b82f479 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs @@ -1,6 +1,7 @@ //@ revisions: edition2024 polonius_alpha //@ ignore-compare-mode-polonius (explicit revisions) //@ edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next //@ check-fail // Tests that liveness for regions in associated types considers outlives From 47e62ee30c6e6c4dd13296dd671c9d1126515324 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 9 Jul 2026 09:32:55 +1000 Subject: [PATCH 60/78] Add a unit test for `TokenStreamIter`. This shows that `size_hint` is imprecise. --- compiler/rustc_ast/src/tokenstream.rs | 3 +++ compiler/rustc_ast/src/tokenstream/tests.rs | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 compiler/rustc_ast/src/tokenstream/tests.rs diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index baad20a86784d..1ef91d718a810 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -22,6 +22,9 @@ use crate::ast_traits::{HasAttrs, HasTokens}; use crate::token::{self, Delimiter, Token, TokenKind}; use crate::{AttrVec, Attribute}; +#[cfg(test)] +mod tests; + /// Part of a `TokenStream`. #[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] pub enum TokenTree { diff --git a/compiler/rustc_ast/src/tokenstream/tests.rs b/compiler/rustc_ast/src/tokenstream/tests.rs new file mode 100644 index 0000000000000..9b79e5fabf1d9 --- /dev/null +++ b/compiler/rustc_ast/src/tokenstream/tests.rs @@ -0,0 +1,13 @@ +use rustc_span::DUMMY_SP; + +use crate::token::TokenKind; +use crate::tokenstream::TokenStream; + +#[test] +fn test_token_stream_iter() { + let ts = TokenStream::token_alone(TokenKind::Eq, DUMMY_SP); + assert_eq!(ts.len(), 1); + + let iter = ts.iter(); + assert_eq!(iter.size_hint(), (0, None)); +} From fa43a690519ca7ea158cd23ba451f27dcef07e46 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 9 Jul 2026 09:25:17 +1000 Subject: [PATCH 61/78] Improve `TokenStreamIter`. It's simpler if it's just a wrapper around `slice::Iter`. Also, we might as well implement `size_hint`. It doesn't have any effect on perf but it's trivial and might help one day. --- compiler/rustc_ast/src/tokenstream.rs | 18 ++++++++---------- compiler/rustc_ast/src/tokenstream/tests.rs | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 1ef91d718a810..98caac2c88f77 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -836,21 +836,18 @@ impl StableHash for TokenStream { } #[derive(Clone)] -pub struct TokenStreamIter<'t> { - stream: &'t TokenStream, - index: usize, -} +pub struct TokenStreamIter<'t>(std::slice::Iter<'t, TokenTree>); impl<'t> TokenStreamIter<'t> { fn new(stream: &'t TokenStream) -> Self { - TokenStreamIter { stream, index: 0 } + TokenStreamIter(stream.0.as_slice().iter()) } // Peeking could be done via `Peekable`, but most iterators need peeking, // and this is simple and avoids the need to use `peekable` and `Peekable` // at all the use sites. pub fn peek(&self) -> Option<&'t TokenTree> { - self.stream.0.get(self.index) + self.0.as_slice().first() } } @@ -858,10 +855,11 @@ impl<'t> Iterator for TokenStreamIter<'t> { type Item = &'t TokenTree; fn next(&mut self) -> Option<&'t TokenTree> { - self.stream.0.get(self.index).map(|tree| { - self.index += 1; - tree - }) + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() } } diff --git a/compiler/rustc_ast/src/tokenstream/tests.rs b/compiler/rustc_ast/src/tokenstream/tests.rs index 9b79e5fabf1d9..6c7e82a97c58e 100644 --- a/compiler/rustc_ast/src/tokenstream/tests.rs +++ b/compiler/rustc_ast/src/tokenstream/tests.rs @@ -9,5 +9,5 @@ fn test_token_stream_iter() { assert_eq!(ts.len(), 1); let iter = ts.iter(); - assert_eq!(iter.size_hint(), (0, None)); + assert_eq!(iter.size_hint(), (1, Some(1))); } From 5af3b45fbe2d8f9809ea5f92efa68b1d20bd5793 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 8 Jul 2026 21:39:07 -0700 Subject: [PATCH 62/78] Use REST API in linkchecker script This changes the linkchecker script to use the REST API instead of the raw endpoint to better deal with rate limits. We've noticed some occasional times when GitHub Actions hits the rate limit. I believe the raw http interface uses IP-based rate limits. On GitHub Actions, you're at the mercy of when other users exhaust the rate limit for the IP your job happens to grab. I believe this should help. This will require updating the repos that use this script in order to benefit. This should be backwards compatible, though. --- src/tools/linkchecker/linkcheck.sh | 33 +++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/tools/linkchecker/linkcheck.sh b/src/tools/linkchecker/linkcheck.sh index d230610a6e79d..36c14d31d7310 100755 --- a/src/tools/linkchecker/linkcheck.sh +++ b/src/tools/linkchecker/linkcheck.sh @@ -23,6 +23,31 @@ set -e +# Fetches the contents of a file from GitHub using the REST API. +# Usage: fetch_github_file OWNER REPO PATH OUTPUT_PATH +fetch_github_file() { + owner="$1" + repo="$2" + path="$3" + output="$4" + + # Hack to repurpose the function's positional parameters to conditionally + # pass curl headers. The GITHUB_TOKEN should be set to help avoid rate + # limits. + if [ -n "${GITHUB_TOKEN:-}" ] + then + set -- -H "Authorization: Bearer $GITHUB_TOKEN" + else + set -- + fi + + curl -L -o "$output" \ + -H "Accept: application/vnd.github.raw" \ + "$@" \ + -H "X-GitHub-Api-Version: 2026-03-10" \ + "https://api.github.com/repos/$owner/$repo/contents/$path" +} + html_dir="$(rustc +nightly --print sysroot)/share/doc/rust/html" if [ ! -d "$html_dir" ] @@ -95,12 +120,10 @@ fi if [ ! -e "linkchecker/main.rs" ] || [ "$iterative" = "0" ] then echo "Downloading linkchecker source..." - nightly_hash=$(rustc +nightly -Vv | grep commit-hash | cut -f2 -d" ") - url="https://raw.githubusercontent.com/rust-lang/rust" mkdir linkchecker - curl -o linkchecker/Cargo.lock ${url}/${nightly_hash}/Cargo.lock - curl -o linkchecker/Cargo.toml ${url}/${nightly_hash}/src/tools/linkchecker/Cargo.toml - curl -o linkchecker/main.rs ${url}/${nightly_hash}/src/tools/linkchecker/main.rs + fetch_github_file rust-lang rust Cargo.lock linkchecker/Cargo.lock + fetch_github_file rust-lang rust src/tools/linkchecker/Cargo.toml linkchecker/Cargo.toml + fetch_github_file rust-lang rust src/tools/linkchecker/main.rs linkchecker/main.rs fi echo "Building book \"$book_name\"..." From 7269b96f233f5ee19548da412bd6e93743f20f61 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Wed, 8 Jul 2026 22:23:58 -0700 Subject: [PATCH 63/78] [compiler] Implement `PartialOrd` via `Ord` for `Span` and newtype_indexes For something unconditionally `Ord`, this is better than using `partial_cmp` on fields. I couldn't find any other cases in the compiler codebase of this, but it's entirely possible I missed some. Let me know if you can think of any. --- compiler/rustc_index_macros/src/newtype.rs | 4 +++- compiler/rustc_span/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_index_macros/src/newtype.rs b/compiler/rustc_index_macros/src/newtype.rs index 4553cf026bb08..9fe2b4caa7229 100644 --- a/compiler/rustc_index_macros/src/newtype.rs +++ b/compiler/rustc_index_macros/src/newtype.rs @@ -138,13 +138,15 @@ impl Parse for Newtype { } } impl ::std::cmp::Ord for #name { + #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_u32().cmp(&other.as_u32()) } } impl ::std::cmp::PartialOrd for #name { + #[inline] fn partial_cmp(&self, other: &Self) -> Option { - self.as_u32().partial_cmp(&other.as_u32()) + Some(self.cmp(other)) } } } diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 5015741f10c5f..ae01956be9301 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -738,7 +738,7 @@ impl Default for SpanData { impl PartialOrd for Span { fn partial_cmp(&self, rhs: &Self) -> Option { - PartialOrd::partial_cmp(&self.data(), &rhs.data()) + Some(self.cmp(rhs)) } } impl Ord for Span { From 5e4558e8d2569d1fa63968ecb5f4de9976c5af57 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 3 May 2026 13:58:31 +0000 Subject: [PATCH 64/78] view-types: add a bunch of tests --- tests/ui/view-types/auxiliary/hygiene.rs | 12 +++++++++ tests/ui/view-types/duplicate_field.rs | 25 ++++++++++++++++++ tests/ui/view-types/hygiene.rs | 33 ++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 tests/ui/view-types/auxiliary/hygiene.rs create mode 100644 tests/ui/view-types/duplicate_field.rs create mode 100644 tests/ui/view-types/hygiene.rs diff --git a/tests/ui/view-types/auxiliary/hygiene.rs b/tests/ui/view-types/auxiliary/hygiene.rs new file mode 100644 index 0000000000000..cf842b56e6970 --- /dev/null +++ b/tests/ui/view-types/auxiliary/hygiene.rs @@ -0,0 +1,12 @@ +//@ edition:2015 + +#![feature(view_types, view_type_macro)] + +pub use std::view::view_type; + +#[macro_export] +macro_rules! view_bar { + () => { + $crate::view_type!(Bar.{ async }) + } +} diff --git a/tests/ui/view-types/duplicate_field.rs b/tests/ui/view-types/duplicate_field.rs new file mode 100644 index 0000000000000..a497bccd1403d --- /dev/null +++ b/tests/ui/view-types/duplicate_field.rs @@ -0,0 +1,25 @@ +//@ known-bug: unknown +//@ run-pass + +#![feature(view_types, view_type_macro)] +#![allow(unused)] + +use std::view::view_type; + +struct Foo { + bar: usize, +} + +struct Pair(usize); + +fn f(_foo: &mut view_type!(Foo.{ bar, bar }), _pair: &mut view_type!(Pair.{ 0, 0 })) {} + +impl Foo { + fn f(self: &mut view_type!(Self.{ bar, bar })) {} +} + +impl Pair { + fn f(self: &mut view_type!(Self.{ 0, 0 })) {} +} + +fn main() {} diff --git a/tests/ui/view-types/hygiene.rs b/tests/ui/view-types/hygiene.rs new file mode 100644 index 0000000000000..f67923a08efeb --- /dev/null +++ b/tests/ui/view-types/hygiene.rs @@ -0,0 +1,33 @@ +//@ known-bug: unknown +//@ run-pass +//@ aux-build: hygiene.rs + +#![feature(view_types, view_type_macro, decl_macro)] +#![allow(unused)] + +extern crate hygiene; + +use std::view::view_type; + +pub macro what($name: ident) { + struct Foo { + bar: usize, + $name: usize, + } + + impl Foo { + fn f(self: &mut view_type!(Self.{ bar, $name })) {} + } +} + +what!(bar); + +struct Bar { + r#async: (), +} + +impl Bar { + fn f(self: hygiene::view_bar!()) {} +} + +fn main() {} From 9ee193eae287691d5914992d81745abbf7d5af4a Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 3 May 2026 13:58:31 +0000 Subject: [PATCH 65/78] view-types: lower view types to HIR --- compiler/rustc_ast_lowering/src/lib.rs | 7 ++++--- compiler/rustc_hir/src/hir.rs | 2 ++ compiler/rustc_hir/src/intravisit.rs | 6 ++++++ .../rustc_hir_analysis/src/hir_ty_lowering/mod.rs | 4 ++++ compiler/rustc_hir_pretty/src/lib.rs | 11 +++++++++++ compiler/rustc_passes/src/input_stats.rs | 1 + src/librustdoc/clean/mod.rs | 4 ++++ src/tools/clippy/clippy_lints/src/dereference.rs | 4 ++++ src/tools/clippy/clippy_utils/src/hir_utils.rs | 4 ++++ 9 files changed, 40 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index dc1acade85ed5..d846a5acb37df 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1750,9 +1750,10 @@ impl<'hir> LoweringContext<'_, 'hir> { ); hir::TyKind::Err(guar) } - TyKind::View(ty, _) => { - // FIXME(scrabsha): lower view types to HIR. - return self.lower_ty(ty, itctx); + TyKind::View(ty, fields) => { + let ty = self.lower_ty_alloc(ty, itctx); + let fields = self.arena.alloc_slice(fields); + hir::TyKind::View(ty, fields) } TyKind::Dummy => panic!("`TyKind::Dummy` should never be lowered"), }; diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index ee401376a6a5c..236ccca1a517e 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3960,6 +3960,8 @@ pub enum TyKind<'hir, Unambig = ()> { /// /// The optional ident is the variant when an enum is passed `field_of!(Enum, Variant.field)`. FieldOf(&'hir Ty<'hir>, &'hir TyFieldPath), + /// A view of a type. `T.{ field_1, field_2 }`. + View(&'hir Ty<'hir>, &'hir [Ident]), /// `TyKind::Infer` means the type should be inferred instead of it having been /// specified. This can appear anywhere in a type. /// diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 44501b1aa892b..df410d680f533 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1053,6 +1053,12 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v, AmbigArg>) - visit_opt!(visitor, visit_ident, *variant); try_visit!(visitor.visit_ident(*field)); } + TyKind::View(ty, fields) => { + try_visit!(visitor.visit_ty_unambig(ty)); + for field in fields { + try_visit!(visitor.visit_ident(*field)); + } + } } V::Result::output() } 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 2b0219f356c3c..b45f74e4d7f52 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3401,6 +3401,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { *variant, *field, ), + hir::TyKind::View(ty, _) => { + // FIXME(scrabsha): lower views to MIR. + return self.lower_ty(ty); + } hir::TyKind::Err(guar) => Ty::new_error(tcx, *guar), }; diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 136fe22eea60f..447ad2447b3b9 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -476,6 +476,17 @@ impl<'a> State<'a> { self.print_ident(*field); self.word(")"); } + hir::TyKind::View(ty, fields) => { + self.word("view_type!("); + self.print_type(ty); + self.word(".{"); + if !fields.is_empty() { + self.space(); + self.commasep(Breaks::Inconsistent, fields, |s, f| s.print_ident(*f)); + self.space(); + } + self.word("})"); + } } self.end(ib) } diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index d2bcf0359342c..39601534d0c85 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -411,6 +411,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { Infer, Pat, FieldOf, + View, Err ] ); diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 3179bda868651..c7387bf157d10 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1953,6 +1953,10 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T TyKind::UnsafeBinder(unsafe_binder_ty) => { UnsafeBinder(Box::new(clean_unsafe_binder_ty(unsafe_binder_ty, cx))) } + TyKind::View(ty, _) => { + // FIXME(scrabsha): propagate view types to `rustdoc`. + clean_ty(ty, cx) + } // Rustdoc handles `TyKind::Err`s by turning them into `Type::Infer`s. TyKind::Infer(()) | TyKind::Err(_) diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index fbff41fe00914..0868ee322a646 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -902,6 +902,10 @@ impl TyCoercionStability { | TyKind::TraitObject(..) | TyKind::InferDelegation(..) | TyKind::Err(_) => Self::Reborrow, + TyKind::View(ty, _) => { + // FIXME(scrabsha): what are the semantics of view types here? + Self::for_hir_ty(ty) + } TyKind::UnsafeBinder(..) => Self::None, }; } diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 050e43344d0c4..7c3fa51228bc9 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -1650,6 +1650,10 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { TyKind::UnsafeBinder(binder) => { self.hash_ty(binder.inner_ty); }, + TyKind::View(ty, _) => { + self.hash_ty(ty); + // FIXME(scrabsha): probably hash the fields as well? + }, TyKind::Err(_) | TyKind::Infer(()) | TyKind::Never From 09b43e400d86f69327c9442108bc6d11bf3712e3 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Mon, 4 May 2026 21:16:40 +0000 Subject: [PATCH 66/78] view-types: error when viewing the same field twice --- .../rustc_hir_analysis/src/diagnostics.rs | 10 ++++++ .../src/hir_ty_lowering/mod.rs | 34 ++++++++++++++++--- tests/ui/view-types/duplicate_field.rs | 13 ++++--- tests/ui/view-types/duplicate_field.stderr | 34 +++++++++++++++++++ tests/ui/view-types/hygiene.rs | 1 - 5 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 tests/ui/view-types/duplicate_field.stderr diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index 1bfc495071284..4f726a46f80b1 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -2000,3 +2000,13 @@ impl Diagnostic<'_, G> for UncoveredTyParam<'_> { diag } } + +#[derive(Diagnostic)] +#[diag("field `{$name}` is already part of the view")] +pub(crate) struct ViewedFieldIsAlreadyPartOfTheView { + #[primary_span] + pub span: Span, + pub name: Symbol, + #[label("field `{$name}` is declared as viewed here")] + pub previous_field_span: Span, +} 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 b45f74e4d7f52..cabe172f2f820 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -51,7 +51,7 @@ use rustc_trait_selection::traits::{self, FulfillmentError}; use tracing::{debug, instrument}; use crate::check::check_abi; -use crate::diagnostics::{BadReturnTypeNotation, NoFieldOnType}; +use crate::diagnostics::{self, BadReturnTypeNotation, NoFieldOnType}; use crate::hir_ty_lowering::errors::{GenericsArgsErrExtend, prohibit_assoc_item_constraint}; use crate::hir_ty_lowering::generics::{check_generic_arg_count, lower_generic_args}; use crate::middle::resolve_bound_vars as rbv; @@ -3401,10 +3401,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { *variant, *field, ), - hir::TyKind::View(ty, _) => { - // FIXME(scrabsha): lower views to MIR. - return self.lower_ty(ty); - } + hir::TyKind::View(ty, fields) => self.lower_view(self.lower_ty(ty), fields), hir::TyKind::Err(guar) => Ty::new_error(tcx, *guar), }; @@ -3816,4 +3813,31 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let adt_ty = Ty::new_adt(tcx, adt_def, args); ty::Const::new_value(tcx, valtree, adt_ty) } + + fn lower_view(&self, inner_ty: Ty<'tcx>, fields: &'tcx [Ident]) -> Ty<'tcx> { + // Step 1: check that every field is unique, and keep a list of field that we know are + // unique. + let mut viewed_fields = Vec::::with_capacity(fields.len()); + + for f in fields { + let f = f.normalize_to_macros_2_0(); + // PERF: this is quadratic, but ~fine since the amount of fields is very low. + if let Some(previous_field_span) = + viewed_fields.iter().find_map(|f_| (*f_ == f).then_some(f_.span)) + { + self.dcx().emit_err(diagnostics::ViewedFieldIsAlreadyPartOfTheView { + name: f.name, + span: f.span, + previous_field_span, + }); + continue; + } + viewed_fields.push(f); + } + + // FIXME(scrabsha): check that `inner_ty` is a struct. + // FIXME(scrabsha): resolve the fields in `viewed_fields` to actual fields. + // FIXME(scrabsha): actually lower view types. + inner_ty + } } diff --git a/tests/ui/view-types/duplicate_field.rs b/tests/ui/view-types/duplicate_field.rs index a497bccd1403d..b45580510b6cb 100644 --- a/tests/ui/view-types/duplicate_field.rs +++ b/tests/ui/view-types/duplicate_field.rs @@ -1,6 +1,3 @@ -//@ known-bug: unknown -//@ run-pass - #![feature(view_types, view_type_macro)] #![allow(unused)] @@ -12,14 +9,22 @@ struct Foo { struct Pair(usize); -fn f(_foo: &mut view_type!(Foo.{ bar, bar }), _pair: &mut view_type!(Pair.{ 0, 0 })) {} +fn f( + _foo: &mut view_type!(Foo.{ bar, bar }), + //~^ ERROR field `bar` is already part of the view + _pair: &mut view_type!(Pair.{ 0, 0 }), + //~^ ERROR field `0` is already part of the view +) { +} impl Foo { fn f(self: &mut view_type!(Self.{ bar, bar })) {} + //~^ ERROR field `bar` is already part of the view } impl Pair { fn f(self: &mut view_type!(Self.{ 0, 0 })) {} + //~^ ERROR field `0` is already part of the view } fn main() {} diff --git a/tests/ui/view-types/duplicate_field.stderr b/tests/ui/view-types/duplicate_field.stderr new file mode 100644 index 0000000000000..f0162f6e0a67d --- /dev/null +++ b/tests/ui/view-types/duplicate_field.stderr @@ -0,0 +1,34 @@ +error: field `bar` is already part of the view + --> $DIR/duplicate_field.rs:13:38 + | +LL | _foo: &mut view_type!(Foo.{ bar, bar }), + | --- ^^^ + | | + | field `bar` is declared as viewed here + +error: field `0` is already part of the view + --> $DIR/duplicate_field.rs:15:38 + | +LL | _pair: &mut view_type!(Pair.{ 0, 0 }), + | - ^ + | | + | field `0` is declared as viewed here + +error: field `bar` is already part of the view + --> $DIR/duplicate_field.rs:21:44 + | +LL | fn f(self: &mut view_type!(Self.{ bar, bar })) {} + | --- ^^^ + | | + | field `bar` is declared as viewed here + +error: field `0` is already part of the view + --> $DIR/duplicate_field.rs:26:42 + | +LL | fn f(self: &mut view_type!(Self.{ 0, 0 })) {} + | - ^ + | | + | field `0` is declared as viewed here + +error: aborting due to 4 previous errors + diff --git a/tests/ui/view-types/hygiene.rs b/tests/ui/view-types/hygiene.rs index f67923a08efeb..15a9f7f228f90 100644 --- a/tests/ui/view-types/hygiene.rs +++ b/tests/ui/view-types/hygiene.rs @@ -1,4 +1,3 @@ -//@ known-bug: unknown //@ run-pass //@ aux-build: hygiene.rs From 55ab4c7a857da8c0c09d9204d13b71291db5a042 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 10 May 2026 19:15:36 +0000 Subject: [PATCH 67/78] view-types: error when used on non-struct types --- .../rustc_hir_analysis/src/diagnostics.rs | 20 ++++++++++++ .../src/hir_ty_lowering/mod.rs | 31 +++++++++++++++++-- compiler/rustc_middle/src/ty/adt.rs | 19 ++++++++++++ tests/ui/view-types/must-be-struct.rs | 7 ++--- tests/ui/view-types/must-be-struct.stderr | 20 ++++++++++++ 5 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 tests/ui/view-types/must-be-struct.stderr diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index 4f726a46f80b1..a671b02b5fa66 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -2010,3 +2010,23 @@ pub(crate) struct ViewedFieldIsAlreadyPartOfTheView { #[label("field `{$name}` is declared as viewed here")] pub previous_field_span: Span, } + +#[derive(Diagnostic)] +#[diag("only structs can be viewed")] +pub(crate) struct OnlyStructsCanBeViewedNonAdt<'tcx> { + #[primary_span] + #[label("type `{$ty}` cannot be viewed")] + pub span: Span, + pub ty: Ty<'tcx>, +} + +#[derive(Diagnostic)] +#[diag("only structs can be viewed")] +pub(crate) struct OnlyStructsCanBeViewedAdt<'tcx> { + #[primary_span] + #[label("`{$ty}` is {$article} {$kind}, it cannot be viewed")] + pub span: Span, + pub ty: Ty<'tcx>, + pub article: &'static str, + pub kind: &'static str, +} 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 cabe172f2f820..86512a7ad389f 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3401,7 +3401,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { *variant, *field, ), - hir::TyKind::View(ty, fields) => self.lower_view(self.lower_ty(ty), fields), + hir::TyKind::View(ty, fields) => { + self.lower_view(self.lower_ty(ty), fields, hir_ty.span) + } + hir::TyKind::Err(guar) => Ty::new_error(tcx, *guar), }; @@ -3814,7 +3817,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ty::Const::new_value(tcx, valtree, adt_ty) } - fn lower_view(&self, inner_ty: Ty<'tcx>, fields: &'tcx [Ident]) -> Ty<'tcx> { + fn lower_view(&self, inner_ty: Ty<'tcx>, fields: &[Ident], ty_span: Span) -> Ty<'tcx> { // Step 1: check that every field is unique, and keep a list of field that we know are // unique. let mut viewed_fields = Vec::::with_capacity(fields.len()); @@ -3835,7 +3838,29 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { viewed_fields.push(f); } - // FIXME(scrabsha): check that `inner_ty` is a struct. + // Step 2: check that the viewed type is a struct. + match inner_ty.kind() { + ty::Adt(def, _) if def.is_struct() => {} + + ty::Adt(def, _) => { + let guar = self.dcx().emit_err(diagnostics::OnlyStructsCanBeViewedAdt { + ty: inner_ty, + span: ty_span, + article: def.article(), + kind: def.descr(), + }); + return Ty::new_error(self.tcx(), guar); + } + + _ => { + let guar = self.dcx().emit_err(diagnostics::OnlyStructsCanBeViewedNonAdt { + ty: inner_ty, + span: ty_span, + }); + return Ty::new_error(self.tcx(), guar); + } + } + // FIXME(scrabsha): resolve the fields in `viewed_fields` to actual fields. // FIXME(scrabsha): actually lower view types. inner_ty diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index d380b7f522636..a0daa04844e03 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -335,6 +335,17 @@ impl From for DataTypeKind { } } +impl AdtKind { + pub fn article(self) -> &'static str { + match self { + AdtKind::Struct => "a", + // https://english.stackexchange.com/a/266324 + AdtKind::Union => "a", + AdtKind::Enum => "an", + } + } +} + impl AdtDefData { /// Creates a new `AdtDefData`. pub(super) fn new( @@ -455,6 +466,14 @@ impl<'tcx> AdtDef<'tcx> { } } + /// Returns a description of this abstract data type with the article. + pub fn article(self) -> &'static str { + match self.adt_kind() { + AdtKind::Struct | AdtKind::Union => "a", + AdtKind::Enum => "an", + } + } + /// Returns a description of a variant of this abstract data type. #[inline] pub fn variant_descr(self) -> &'static str { diff --git a/tests/ui/view-types/must-be-struct.rs b/tests/ui/view-types/must-be-struct.rs index 43dbae587e4ab..fb30ef1234cc5 100644 --- a/tests/ui/view-types/must-be-struct.rs +++ b/tests/ui/view-types/must-be-struct.rs @@ -1,6 +1,3 @@ -//@ known-bug: unknown -//@ run-pass - #![feature(view_types, view_type_macro)] #![allow(unused)] @@ -11,9 +8,11 @@ enum Foo { Baz, } -// The following types are not structs, we expect errors here. fn f(_: view_type!(Foo.{})) {} +//~^ ERROR only structs can be viewed fn g(_: view_type!(u8.{})) {} +//~^ ERROR only structs can be viewed fn h(_: view_type!(char.{})) {} +//~^ ERROR only structs can be viewed fn main() {} diff --git a/tests/ui/view-types/must-be-struct.stderr b/tests/ui/view-types/must-be-struct.stderr new file mode 100644 index 0000000000000..3d47e4903af6c --- /dev/null +++ b/tests/ui/view-types/must-be-struct.stderr @@ -0,0 +1,20 @@ +error: only structs can be viewed + --> $DIR/must-be-struct.rs:11:9 + | +LL | fn f(_: view_type!(Foo.{})) {} + | ^^^^^^^^^^^^^^^^^^ `Foo` is an enum, it cannot be viewed + +error: only structs can be viewed + --> $DIR/must-be-struct.rs:13:9 + | +LL | fn g(_: view_type!(u8.{})) {} + | ^^^^^^^^^^^^^^^^^ type `u8` cannot be viewed + +error: only structs can be viewed + --> $DIR/must-be-struct.rs:15:9 + | +LL | fn h(_: view_type!(char.{})) {} + | ^^^^^^^^^^^^^^^^^^^ type `char` cannot be viewed + +error: aborting due to 3 previous errors + From 1390f89d1c983e8c83939c7cfc9ef4afe2089853 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Mon, 4 May 2026 21:16:40 +0000 Subject: [PATCH 68/78] view-types: resolve viewed fields --- .../src/hir_ty_lowering/mod.rs | 26 ++++++++-- tests/ui/view-types/must-exist.rs | 6 +-- tests/ui/view-types/must-exist.stderr | 15 ++++++ tests/ui/view-types/syntax-errors.rs | 10 ++-- tests/ui/view-types/syntax-errors.stderr | 47 ++++++++++++++----- 5 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 tests/ui/view-types/must-exist.stderr 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 86512a7ad389f..7409ea620228d 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3839,8 +3839,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } // Step 2: check that the viewed type is a struct. - match inner_ty.kind() { - ty::Adt(def, _) if def.is_struct() => {} + let variant = match inner_ty.kind() { + ty::Adt(def, _) if def.is_struct() => def.non_enum_variant(), ty::Adt(def, _) => { let guar = self.dcx().emit_err(diagnostics::OnlyStructsCanBeViewedAdt { @@ -3859,9 +3859,29 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }); return Ty::new_error(self.tcx(), guar); } + }; + + // Step 3: check that every viewed field exists. + let mut viewed_indices = Vec::with_capacity(viewed_fields.len()); + let mut error = None; + for field in viewed_fields { + let Some((_, field)) = variant + .fields + .iter_enumerated() + .find(|(_, f)| f.ident(self.tcx()).normalize_to_macros_2_0() == field) + else { + let err = + self.dcx().emit_err(NoFieldOnType { span: field.span, field, ty: inner_ty }); + error = Some(err); + continue; + }; + + viewed_indices.push(field); + } + if let Some(guar) = error { + return Ty::new_error(self.tcx(), guar); } - // FIXME(scrabsha): resolve the fields in `viewed_fields` to actual fields. // FIXME(scrabsha): actually lower view types. inner_ty } diff --git a/tests/ui/view-types/must-exist.rs b/tests/ui/view-types/must-exist.rs index 05792b01b3265..efb43eb509e9c 100644 --- a/tests/ui/view-types/must-exist.rs +++ b/tests/ui/view-types/must-exist.rs @@ -1,6 +1,3 @@ -//@ known-bug: unknown -//@ run-pass - #![feature(view_types, view_type_macro)] #![allow(unused)] @@ -10,8 +7,9 @@ struct S { foo: (), } -// We expect errors here, since `S` has no field `bar`. fn f(_: view_type!(S.{ bar })) {} +//~^ ERROR no field `bar` on type `S` fn g(_: view_type!(S.{ foo, bar })) {} +//~^ ERROR no field `bar` on type `S` fn main() {} diff --git a/tests/ui/view-types/must-exist.stderr b/tests/ui/view-types/must-exist.stderr new file mode 100644 index 0000000000000..a775ad84a7edf --- /dev/null +++ b/tests/ui/view-types/must-exist.stderr @@ -0,0 +1,15 @@ +error[E0609]: no field `bar` on type `S` + --> $DIR/must-exist.rs:10:24 + | +LL | fn f(_: view_type!(S.{ bar })) {} + | ^^^ + +error[E0609]: no field `bar` on type `S` + --> $DIR/must-exist.rs:12:29 + | +LL | fn g(_: view_type!(S.{ foo, bar })) {} + | ^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0609`. diff --git a/tests/ui/view-types/syntax-errors.rs b/tests/ui/view-types/syntax-errors.rs index 19f5061cf820a..18ccb859d549e 100644 --- a/tests/ui/view-types/syntax-errors.rs +++ b/tests/ui/view-types/syntax-errors.rs @@ -9,13 +9,17 @@ struct Foo { } impl Foo { - fn not_a_field(self: &mut view_type!(Foo.{ _ }), _: &mut view_type!(Foo.{ _ })) {} - //~^ ERROR expected identifier - //~| ERROR expected identifier + fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + //~^ ERROR expected identifier, found reserved identifier + //~| ERROR expected identifier, found reserved identifier + //~| ERROR no field `_` on type `Foo` + //~| ERROR no field `_` on type `Foo` fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} //~^ ERROR expected identifier //~| ERROR expected identifier + //~| ERROR no field `r#where` on type `Foo` + //~| ERROR no field `r#for` on type `Foo` fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!(Foo.{ bar baz })) {} //~^ ERROR expected one of diff --git a/tests/ui/view-types/syntax-errors.stderr b/tests/ui/view-types/syntax-errors.stderr index 86c167ca9a879..1c1e5fdafae1a 100644 --- a/tests/ui/view-types/syntax-errors.stderr +++ b/tests/ui/view-types/syntax-errors.stderr @@ -1,17 +1,17 @@ error: expected identifier, found reserved identifier `_` - --> $DIR/syntax-errors.rs:12:48 + --> $DIR/syntax-errors.rs:12:49 | -LL | fn not_a_field(self: &mut view_type!(Foo.{ _ }), _: &mut view_type!(Foo.{ _ })) {} - | ^ expected identifier, found reserved identifier +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ expected identifier, found reserved identifier error: expected identifier, found reserved identifier `_` - --> $DIR/syntax-errors.rs:12:79 + --> $DIR/syntax-errors.rs:12:80 | -LL | fn not_a_field(self: &mut view_type!(Foo.{ _ }), _: &mut view_type!(Foo.{ _ })) {} - | ^ expected identifier, found reserved identifier +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ expected identifier, found reserved identifier error: expected identifier, found keyword `where` - --> $DIR/syntax-errors.rs:16:44 + --> $DIR/syntax-errors.rs:18:44 | LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} | ^^^^^ expected identifier, found keyword @@ -22,7 +22,7 @@ LL | fn keyword(self: &mut view_type!(Foo.{ r#where }), _: &mut view_type!(F | ++ error: expected identifier, found keyword `for` - --> $DIR/syntax-errors.rs:16:79 + --> $DIR/syntax-errors.rs:18:79 | LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} | ^^^ expected identifier, found keyword @@ -33,7 +33,7 @@ LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo | ++ error: expected one of `,` or `}`, found `baz` - --> $DIR/syntax-errors.rs:20:49 + --> $DIR/syntax-errors.rs:24:49 | LL | fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!(Foo.{ bar baz })) {} | -^^^ expected one of `,` or `}` @@ -41,12 +41,37 @@ LL | fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!( | help: missing `,` error: expected one of `,` or `}`, found `baz` - --> $DIR/syntax-errors.rs:20:86 + --> $DIR/syntax-errors.rs:24:86 | LL | fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!(Foo.{ bar baz })) {} | -^^^ expected one of `,` or `}` | | | help: missing `,` -error: aborting due to 6 previous errors +error[E0609]: no field `_` on type `Foo` + --> $DIR/syntax-errors.rs:12:49 + | +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ + +error[E0609]: no field `_` on type `Foo` + --> $DIR/syntax-errors.rs:12:80 + | +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ + +error[E0609]: no field `r#where` on type `Foo` + --> $DIR/syntax-errors.rs:18:44 + | +LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} + | ^^^^^ + +error[E0609]: no field `r#for` on type `Foo` + --> $DIR/syntax-errors.rs:18:79 + | +LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} + | ^^^ + +error: aborting due to 10 previous errors +For more information about this error, try `rustc --explain E0609`. From 77d7cf2ce5b2364c2aa8807d7a30e3e4fb5767f5 Mon Sep 17 00:00:00 2001 From: okaneco <47607823+okaneco@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:15:44 -0400 Subject: [PATCH 69/78] Implement feature `char_to_u32` Add method `char::to_u32` which returns the underlying value of `char`. --- library/core/src/char/methods.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index d0114c30a6b3c..2af5b4ef46b97 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -1812,6 +1812,28 @@ impl char { ToCasefold(CaseMappingIter::new(conversions::to_casefold(self))) } + /// Returns the code point value as a `u32`. + /// + /// # Examples + /// + /// ``` + /// #![feature(char_to_u32)] + /// + /// let ascii = 'a'; + /// let heart = '❤'; + /// + /// assert_eq!(ascii.to_u32(), 97_u32); + /// assert_eq!(heart.to_u32(), 0x2764_u32); + /// ``` + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[unstable(feature = "char_to_u32", issue = "158938")] + #[rustc_const_unstable(feature = "char_to_u32", issue = "158938")] + #[inline(always)] + pub const fn to_u32(self) -> u32 { + self as u32 + } + /// Checks if the value is within the ASCII range. /// /// # Examples From e732dd16fe82ec7400ad835bccc1ebfcfc43c0a4 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 9 Jul 2026 11:36:51 +0200 Subject: [PATCH 70/78] configure out 32-bit transcendental functions on x86 msvc we know that the symbols are not available there --- library/core/src/num/imp/libm.rs | 57 +++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/library/core/src/num/imp/libm.rs b/library/core/src/num/imp/libm.rs index c67adc337c1cb..388f1479b2461 100644 --- a/library/core/src/num/imp/libm.rs +++ b/library/core/src/num/imp/libm.rs @@ -78,54 +78,65 @@ unsafe extern "C" { /// These symbols will be available when `std` is available, and on many no-std platforms. However, /// since this isn't a guarantee, we cannot rely on them for stable implementations. pub(crate) mod likely_available { + cfg_select! { + all(target_env = "msvc", target_arch = "x86") => { + /* these symbols are not available on x86 msvc */ + } + _ => { + #[allow(dead_code)] + unsafe extern "C" { + pub(crate) safe fn acosf(n: f32) -> f32; + pub(crate) safe fn asinf(n: f32) -> f32; + pub(crate) safe fn atan2f(a: f32, b: f32) -> f32; + pub(crate) safe fn atanf(n: f32) -> f32; + pub(crate) safe fn cosf(x: f32) -> f32; + pub(crate) safe fn coshf(n: f32) -> f32; + pub(crate) safe fn erfcf(x: f32) -> f32; + pub(crate) safe fn erff(x: f32) -> f32; + pub(crate) safe fn exp2f(x: f32) -> f32; + pub(crate) safe fn expf(x: f32) -> f32; + pub(crate) safe fn expm1f(n: f32) -> f32; + pub(crate) safe fn hypotf(x: f32, y: f32) -> f32; + pub(crate) safe fn ldexpf(f: f32, n: i32) -> f32; + pub(crate) safe fn log10f(x: f32) -> f32; + pub(crate) safe fn log1pf(n: f32) -> f32; + pub(crate) safe fn log2f(x: f32) -> f32; + pub(crate) safe fn logf(x: f32) -> f32; + pub(crate) safe fn powf(x: f32, y: f32) -> f32; + pub(crate) safe fn sinf(x: f32) -> f32; + pub(crate) safe fn sinhf(n: f32) -> f32; + pub(crate) safe fn tanf(n: f32) -> f32; + pub(crate) safe fn tanhf(n: f32) -> f32; + pub(crate) safe fn tgammaf(x: f32) -> f32; + } + } + } + #[allow(dead_code)] unsafe extern "C" { pub(crate) safe fn acos(x: f64) -> f64; - pub(crate) safe fn acosf(n: f32) -> f32; pub(crate) safe fn asin(x: f64) -> f64; - pub(crate) safe fn asinf(n: f32) -> f32; pub(crate) safe fn atan(x: f64) -> f64; pub(crate) safe fn atan2(x: f64, y: f64) -> f64; - pub(crate) safe fn atan2f(a: f32, b: f32) -> f32; - pub(crate) safe fn atanf(n: f32) -> f32; pub(crate) safe fn cos(x: f64) -> f64; - pub(crate) safe fn cosf(x: f32) -> f32; pub(crate) safe fn cosh(x: f64) -> f64; - pub(crate) safe fn coshf(n: f32) -> f32; pub(crate) safe fn erf(x: f64) -> f64; pub(crate) safe fn erfc(x: f64) -> f64; - pub(crate) safe fn erfcf(x: f32) -> f32; - pub(crate) safe fn erff(x: f32) -> f32; pub(crate) safe fn exp(x: f64) -> f64; pub(crate) safe fn exp2(x: f64) -> f64; - pub(crate) safe fn exp2f(x: f32) -> f32; - pub(crate) safe fn expf(x: f32) -> f32; pub(crate) safe fn expm1(x: f64) -> f64; - pub(crate) safe fn expm1f(n: f32) -> f32; pub(crate) safe fn hypot(x: f64, y: f64) -> f64; - pub(crate) safe fn hypotf(x: f32, y: f32) -> f32; pub(crate) safe fn ldexp(f: f64, n: i32) -> f64; - pub(crate) safe fn ldexpf(f: f32, n: i32) -> f32; pub(crate) safe fn log(x: f64) -> f64; pub(crate) safe fn log10(x: f64) -> f64; - pub(crate) safe fn log10f(x: f32) -> f32; pub(crate) safe fn log1p(x: f64) -> f64; - pub(crate) safe fn log1pf(n: f32) -> f32; pub(crate) safe fn log2(x: f64) -> f64; - pub(crate) safe fn log2f(x: f32) -> f32; - pub(crate) safe fn logf(x: f32) -> f32; pub(crate) safe fn pow(x: f64, y: f64) -> f64; - pub(crate) safe fn powf(x: f32, y: f32) -> f32; pub(crate) safe fn sin(x: f64) -> f64; - pub(crate) safe fn sinf(x: f32) -> f32; pub(crate) safe fn sinh(x: f64) -> f64; - pub(crate) safe fn sinhf(n: f32) -> f32; pub(crate) safe fn tan(x: f64) -> f64; - pub(crate) safe fn tanf(n: f32) -> f32; pub(crate) safe fn tanh(x: f64) -> f64; - pub(crate) safe fn tanhf(n: f32) -> f32; pub(crate) safe fn tgamma(x: f64) -> f64; - pub(crate) safe fn tgammaf(x: f32) -> f32; } } From da925efa9e459b3730c856cb3927fba38a341c2d Mon Sep 17 00:00:00 2001 From: Justin Schilleman Date: Thu, 9 Jul 2026 09:46:12 +0000 Subject: [PATCH 71/78] Add documentation for the `no_std` attribute * initial writing of the `no_std` attribute * remove unnecessary `Vec` & `String` links * fix incorrect casing, remove redundant sentence, and make it more explicit talking about `alloc` --- library/core/src/attribute_docs.rs | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/library/core/src/attribute_docs.rs b/library/core/src/attribute_docs.rs index e3679ecd4d309..94ff7a8fee6d3 100644 --- a/library/core/src/attribute_docs.rs +++ b/library/core/src/attribute_docs.rs @@ -349,3 +349,59 @@ mod deprecated_attribute {} /// [`deny`]: ./attribute.deny.html /// [`forbid`]: ./attribute.forbid.html mod warn_attribute {} + +#[doc(attribute = "no_std")] +// +/// Prevents automatically linking the standard library. +/// +/// Written as an inner attribute at the top of the crate root, the `no_std` attribute stops +/// the compiler from linking [`std`] into the crate, and only links [`core`]. +/// +/// The attribute also swaps which prelude gets inserted, with the [`core`] prelude replacing +/// the [`std`] prelude. Items like [`Option`], [`Result`], and the primitive types remain +/// available from [`core`] without any imports needed: +/// +/// ```rust,ignore (no_std) +/// #![no_std] +/// +/// fn halve(x: u32) -> Option { +/// if x % 2 == 0 { Some(x / 2) } else { None } +/// } +/// ``` +/// +/// Anything that requires heap memory allocations is not part of [`core`]. Linking the [`alloc`] +/// crate explicitly can be used to include these types: +/// +/// ```rust,ignore (no_std + needs a global allocator to link) +/// #![no_std] +/// +/// extern crate alloc; +/// +/// use alloc::vec::Vec; +/// ``` +/// +/// A `no_std` binary also removes the startup routine and default panic handler `std` +/// normally provides. It must define its own `#[panic_handler]`, and typically its own +/// entry point by additionally using `#![no_main]`: +/// +/// ```rust,ignore (no_std + needs more than no_main alone to link) +/// #![no_std] +/// #![no_main] +/// +/// use core::panic::PanicInfo; +/// +/// #[panic_handler] +/// fn on_panic(_info: &PanicInfo) -> ! { +/// loop {} +/// } +/// ``` +/// +/// For more information, see the Reference on [the `no_std` attribute]. +/// +/// [`std`]: ../std/index.html +/// [`core`]: ../core/index.html +/// [`alloc`]: ../alloc/index.html +/// [`Option`]: option::Option +/// [`Result`]: result::Result +/// [the `no_std` attribute]: ../reference/names/preludes.html#the-no_std-attribute +mod no_std_attribute {} From 8ecf21cdb05e7631f440314ffdb1daf2f8f83efb Mon Sep 17 00:00:00 2001 From: blyxyas Date: Thu, 9 Jul 2026 12:18:12 +0000 Subject: [PATCH 72/78] Propagate error downwards and emit it, even if not related to file extension. Fix tests and update docs. --- compiler/rustc_driver_impl/src/lib.rs | 5 +---- src/doc/unstable-book/src/compiler-flags/ls.md | 2 +- tests/run-make/ls-metadata/rmake.rs | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index f315d03a00986..b4a6cd33a60f4 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -606,7 +606,6 @@ fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { &sess.opts.unstable_opts.ls, sess.cfg_version, ) { - // If the panic comes from the input being the wrong file, direct the user to .rmeta files if path.extension().is_some_and(|extension| extension == "rs") { let mut err = sess .dcx() @@ -617,9 +616,7 @@ fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { } err.emit(); } - - // Else, just panic (This shouldn't be reached); - panic!("{error}"); + sess.dcx().fatal(error.to_string()); } safe_println!("{}", String::from_utf8(v).unwrap()); } diff --git a/src/doc/unstable-book/src/compiler-flags/ls.md b/src/doc/unstable-book/src/compiler-flags/ls.md index abb85acd1e58f..812f2eee1d57f 100644 --- a/src/doc/unstable-book/src/compiler-flags/ls.md +++ b/src/doc/unstable-book/src/compiler-flags/ls.md @@ -12,7 +12,7 @@ Allowed values are: - `root`: Crate info. - `lang_items`: Language items used and missing, if any. -- `features`: Library features used. +- `features`: Library features defined via the `#[stable]` and `#[unstable]` internal attributes. - `items`: All items (such as modules, functions...) in the crate, including attributes like their visibility - `all`: All of the above diff --git a/tests/run-make/ls-metadata/rmake.rs b/tests/run-make/ls-metadata/rmake.rs index 42db97a292bed..3fc7c911f6da9 100644 --- a/tests/run-make/ls-metadata/rmake.rs +++ b/tests/run-make/ls-metadata/rmake.rs @@ -10,7 +10,7 @@ use run_make_support::{rfs, rustc}; fn main() { rustc().input("foo.rs").run(); - rustc().arg("-Zls=root").input("foo").run(); + rustc().arg("-Zls=root").input("foo").run_fail(); rfs::create_file("bar"); - rustc().arg("-Zls=root").input("bar").run(); + rustc().arg("-Zls=root").input("bar").run_fail(); } From ab8da78b45a96cf6a706219a8216a52cb8382da2 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Thu, 9 Jul 2026 13:42:34 +0000 Subject: [PATCH 73/78] Do not write errors to `out`, return Err(..) directly --- compiler/rustc_metadata/src/locator.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index f0502952bd2fa..78d06add675dc 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -963,10 +963,7 @@ pub fn list_file_metadata( let flavor = get_flavor_from_path(path); match get_metadata_section(target, flavor, path, metadata_loader, cfg_version, None) { Ok(metadata) => metadata.list_crate_metadata(out, ls_kinds), - Err(msg) => { - let _ = write!(out, "{msg}"); - Err(IoError::new(IoErrorKind::Other, msg.to_string())) - } + Err(msg) => Err(IoError::new(IoErrorKind::Other, msg.to_string())), } } From 055af58420340f6e7f2fb51d482a39de2b94db15 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Thu, 9 Jul 2026 14:24:32 +0000 Subject: [PATCH 74/78] Remove compile-fail --- .../alias-liveness/gat-outlives-implied.edition2024.stderr | 2 +- .../alias-liveness/gat-outlives-implied.polonius_alpha.stderr | 2 +- tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr index 2d733d142f0f6..9317846041925 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr @@ -1,5 +1,5 @@ error[E0506]: cannot assign to `cluster` because it is borrowed - --> $DIR/gat-outlives-implied.rs:20:9 + --> $DIR/gat-outlives-implied.rs:19:9 | LL | let changes = self.changes(&cluster); | -------- `cluster` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr index 2d733d142f0f6..9317846041925 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr @@ -1,5 +1,5 @@ error[E0506]: cannot assign to `cluster` because it is borrowed - --> $DIR/gat-outlives-implied.rs:20:9 + --> $DIR/gat-outlives-implied.rs:19:9 | LL | let changes = self.changes(&cluster); | -------- `cluster` is borrowed here diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs index 223075b82f479..a9443fccc76b4 100644 --- a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs @@ -2,7 +2,6 @@ //@ ignore-compare-mode-polonius (explicit revisions) //@ edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next -//@ check-fail // Tests that liveness for regions in associated types considers outlives // bounds, and the transitive implied outlives bounds from those. From ebe343bb003c7418e4569569bd018aea1e5dd39f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 9 Jul 2026 08:16:27 -0700 Subject: [PATCH 75/78] Clean up redundant explicit intra-doc link This is a weird, but unrelated, problem with doc comments and attribute macros. If I remove the `#[instrument]` attribute, the lint behaves the way it's supposed to, and points directly at the link instead of pointing at the whole line. warning: redundant explicit link target --> compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs:2373:5 | 2373 | /// Lower a [`hir::ConstArg`] to a (type-level) [`ty::Const`](Const). | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ explicit target `Const` is redundant because label `ty::Const` resolves to same destination | = note: when a link's destination is not specified, the label is used to resolve intra-doc links = note: `#[warn(rustdoc::redundant_explicit_links)]` on by default --- compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 baa9fddc2a651..5fd632f9b000f 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -2302,7 +2302,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self.check_param_uses_if_mcg(ct, tcx.hir_span(path_hir_id), false) } - /// Lower a [`hir::ConstArg`] to a (type-level) [`ty::Const`](Const). + /// Lower a [`hir::ConstArg`] to a (type-level) [`ty::Const`]. #[instrument(skip(self), level = "debug")] pub fn lower_const_arg(&self, const_arg: &hir::ConstArg<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { let tcx = self.tcx(); From c3257a591f9c1e22f2f5755e23d99032f6f7fb0d Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 12 May 2026 16:12:53 +0200 Subject: [PATCH 76/78] Infer all anonymous lifetimes in (assoc) consts as `'static` Hard error on hidden elided lifetimes (`S` instead of `S<'_>`) in assoc consts --- compiler/rustc_lint_defs/src/builtin.rs | 43 ---- compiler/rustc_resolve/src/diagnostics.rs | 22 -- compiler/rustc_resolve/src/late.rs | 216 +++++++----------- .../ui/consts/assoc-const-elided-lifetime.rs | 6 +- .../consts/assoc-const-elided-lifetime.stderr | 44 ---- .../elided-lifetime.rs | 7 +- .../elided-lifetime.stderr | 53 ----- .../generic-associated-const.rs | 6 +- .../generic-associated-const.stderr | 37 --- .../static-trait-impl.rs | 7 +- .../static-trait-impl.stderr | 35 --- .../missing-lifetime-in-assoc-const-type.rs | 5 + 12 files changed, 89 insertions(+), 392 deletions(-) delete mode 100644 tests/ui/consts/assoc-const-elided-lifetime.stderr delete mode 100644 tests/ui/consts/static-default-lifetime/elided-lifetime.stderr delete mode 100644 tests/ui/consts/static-default-lifetime/generic-associated-const.stderr delete mode 100644 tests/ui/consts/static-default-lifetime/static-trait-impl.stderr diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index d38b1cf47bd6f..693737bd89bdd 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -44,7 +44,6 @@ pub mod hardwired { DEPRECATED_WHERE_CLAUSE_LOCATION, DUPLICATE_FEATURES, DUPLICATE_MACRO_ATTRIBUTES, - ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT, ELIDED_LIFETIMES_IN_PATHS, EXPLICIT_BUILTIN_CFGS_IN_FLAGS, EXPORTED_PRIVATE_DEPENDENCIES, @@ -4762,48 +4761,6 @@ declare_lint! { "impl trait in impl method signature does not match trait method signature", } -declare_lint! { - /// The `elided_lifetimes_in_associated_constant` lint detects elided lifetimes - /// in associated constants when there are other lifetimes in scope. This was - /// accidentally supported, and this lint was later relaxed to allow eliding - /// lifetimes to `'static` when there are no lifetimes in scope. - /// - /// ### Example - /// - /// ```rust,compile_fail - /// #![deny(elided_lifetimes_in_associated_constant)] - /// - /// struct Foo<'a>(&'a ()); - /// - /// impl<'a> Foo<'a> { - /// const STR: &str = "hello, world"; - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// Previous version of Rust - /// - /// Implicit static-in-const behavior was decided [against] for associated - /// constants because of ambiguity. This, however, regressed and the compiler - /// erroneously treats elided lifetimes in associated constants as lifetime - /// parameters on the impl. - /// - /// This is a [future-incompatible] lint to transition this to a - /// hard error in the future. - /// - /// [against]: https://github.com/rust-lang/rust/issues/38831 - /// [future-incompatible]: ../index.md#future-incompatible-lints - pub ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT, - Deny, - "elided lifetimes cannot be used in associated constants in impls", - @future_incompatible = FutureIncompatibleInfo { - reason: fcw!(FutureReleaseError #115010), - }; -} - declare_lint! { /// The `private_macro_use` lint detects private macros that are imported /// with `#[macro_use]`. diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 2baf423e296d6..22cc6a581f19a 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1661,28 +1661,6 @@ pub(crate) struct UnusedQualifications { pub removal_span: Span, } -#[derive(Diagnostic)] -#[diag( - "{$elided -> - [true] `&` without an explicit lifetime name cannot be used here - *[false] `'_` cannot be used here - }" -)] -pub(crate) struct AssociatedConstElidedLifetime { - #[suggestion( - "use the `'static` lifetime", - style = "verbose", - code = "{code}", - applicability = "machine-applicable" - )] - pub span: Span, - - pub code: &'static str, - pub elided: bool, - #[note("cannot automatically infer `'static` because of other lifetimes in scope")] - pub lifetimes_in_scope: MultiSpan, -} - #[derive(Diagnostic)] #[diag("lifetime parameter `{$ident}` only used once")] pub(crate) struct SingleUseLifetime { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index c94f2f3ffb85d..4ae56ac7c6f3d 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -333,7 +333,11 @@ enum LifetimeRibKind { AnonymousCreateParameter { binder: NodeId, report_in_path: bool }, /// Replace all anonymous lifetimes by provided lifetime. - Elided(LifetimeRes), + Elided { + res: LifetimeRes, + /// Always report those lifetimes as an error if in a path + error_in_path: bool, + }, // -- Barrier ribs that stop lifetime lookup, or continue it but produce an error later. // @@ -342,11 +346,6 @@ enum LifetimeRibKind { /// error on default object bounds (e.g., `Box`). AnonymousReportError, - /// Resolves elided lifetimes to `'static` if there are no other lifetimes in scope, - /// otherwise give a warning that the previous behavior of introducing a new early-bound - /// lifetime is a bug and will be removed (if `emit_lint` is enabled). - StaticIfNoLifetimeInScope { lint_id: NodeId, emit_lint: bool }, - /// Signal we cannot find which should be the anonymous lifetime. ElisionFailure, @@ -369,6 +368,12 @@ enum LifetimeRibKind { /// Lifetimes cannot be elided in `impl Trait` types without `#![feature(anonymous_lifetime_in_impl_trait)]`. ImplTrait, } +impl LifetimeRibKind { + /// Convenience function for creating non-erroring `Elided` variants. + fn elided(res: LifetimeRes) -> LifetimeRibKind { + LifetimeRibKind::Elided { res, error_in_path: false } + } +} #[derive(Copy, Clone, Debug)] enum LifetimeBinderKind { @@ -1162,7 +1167,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc this.last_block_rib = None; // Resolve the function body, potentially inside the body of an async closure this.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Infer), + LifetimeRibKind::elided(LifetimeRes::Infer), |this| this.visit_block(body), ); @@ -1190,7 +1195,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc this.with_lifetime_rib( match binder { ClosureBinder::NotPresent => { - LifetimeRibKind::Elided(LifetimeRes::Infer) + LifetimeRibKind::elided(LifetimeRes::Infer) } ClosureBinder::For { .. } => LifetimeRibKind::AnonymousReportError, }, @@ -1202,7 +1207,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc let previous_state = replace(&mut this.in_func_body, true); // Resolve the function body, potentially inside the body of an async closure this.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Infer), + LifetimeRibKind::elided(LifetimeRes::Infer), |this| this.visit_expr(body), ); @@ -1380,9 +1385,8 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc } LifetimeRibKind::AnonymousCreateParameter { .. } | LifetimeRibKind::AnonymousReportError - | LifetimeRibKind::StaticIfNoLifetimeInScope { .. } | LifetimeRibKind::ImplTrait - | LifetimeRibKind::Elided(_) + | LifetimeRibKind::Elided { .. } | LifetimeRibKind::ElisionFailure | LifetimeRibKind::ConcreteAnonConst(_) | LifetimeRibKind::ConstParamTy => {} @@ -1786,7 +1790,6 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { // lifetime would be illegal. LifetimeRibKind::Item | LifetimeRibKind::AnonymousReportError - | LifetimeRibKind::StaticIfNoLifetimeInScope { .. } | LifetimeRibKind::ElisionFailure => Some(LifetimeUseSet::Many), // An anonymous lifetime is legal here, and bound to the right // place, go ahead. @@ -1800,11 +1803,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { }), // Only report if eliding the lifetime would have the same // semantics. - LifetimeRibKind::Elided(r) => Some(if res == r { - LifetimeUseSet::One { use_span: ident.span, use_ctxt } - } else { - LifetimeUseSet::Many - }), + LifetimeRibKind::Elided { res: r, error_in_path } => { + Some(if res == r && !error_in_path { + LifetimeUseSet::One { use_span: ident.span, use_ctxt } + } else { + LifetimeUseSet::Many + }) + } LifetimeRibKind::Generics { .. } | LifetimeRibKind::ConstParamTy => None, LifetimeRibKind::ConcreteAnonConst(_) => { @@ -1845,12 +1850,11 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { return; } LifetimeRibKind::AnonymousCreateParameter { .. } - | LifetimeRibKind::Elided(_) + | LifetimeRibKind::Elided { .. } | LifetimeRibKind::Generics { .. } | LifetimeRibKind::ElisionFailure | LifetimeRibKind::AnonymousReportError - | LifetimeRibKind::ImplTrait - | LifetimeRibKind::StaticIfNoLifetimeInScope { .. } => {} + | LifetimeRibKind::ImplTrait => {} } } @@ -1889,48 +1893,6 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.record_lifetime_use(lifetime.id, res, elision_candidate); return; } - LifetimeRibKind::StaticIfNoLifetimeInScope { lint_id: node_id, emit_lint } => { - let mut lifetimes_in_scope = vec![]; - for rib in self.lifetime_ribs[..i].iter().rev() { - lifetimes_in_scope.extend(rib.bindings.iter().map(|(ident, _)| ident.span)); - // Consider any anonymous lifetimes, too - if let LifetimeRibKind::AnonymousCreateParameter { binder, .. } = rib.kind - && let Some(extra) = self.r.extra_lifetime_params_map.get(&binder) - { - lifetimes_in_scope.extend(extra.iter().map(|(ident, _, _)| ident.span)); - } - if let LifetimeRibKind::Item = rib.kind { - break; - } - } - if lifetimes_in_scope.is_empty() { - self.record_lifetime_use( - lifetime.id, - LifetimeRes::Static, - elision_candidate, - ); - return; - } else if emit_lint { - let lt_span = if elided { - lifetime.ident.span.shrink_to_hi() - } else { - lifetime.ident.span - }; - let code = if elided { "'static " } else { "'static" }; - - self.r.lint_buffer.buffer_lint( - lint::builtin::ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT, - node_id, - lifetime.ident.span, - crate::diagnostics::AssociatedConstElidedLifetime { - elided, - code, - span: lt_span, - lifetimes_in_scope: lifetimes_in_scope.into(), - }, - ); - } - } LifetimeRibKind::AnonymousReportError => { let guar = if elided { let suggestion = if self.diag_metadata.in_assoc_ty_binding { @@ -2047,7 +2009,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.record_lifetime_err(lifetime.id, guar); return; } - LifetimeRibKind::Elided(res) => { + LifetimeRibKind::Elided { res, .. } => { self.record_lifetime_use(lifetime.id, res, elision_candidate); return; } @@ -2317,7 +2279,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { // impl Foo for std::cell::Ref // note lack of '_ // async fn foo(_: std::cell::Ref) { ... } LifetimeRibKind::AnonymousCreateParameter { report_in_path: true, .. } - | LifetimeRibKind::StaticIfNoLifetimeInScope { .. } => { + | LifetimeRibKind::Elided { error_in_path: true, .. } => { let sess = self.r.tcx.sess; let subdiag = elided_lifetime_in_path_suggestion( sess.source_map(), @@ -2353,7 +2315,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } break; } - LifetimeRibKind::Elided(res) => { + LifetimeRibKind::Elided { res, error_in_path: false } => { let mut candidate = LifetimeElisionCandidate::Missing(missing_lifetime); for id in node_ids { self.record_lifetime_use( @@ -2480,7 +2442,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { if fn_id == this.r.current_owner.id { this.r.current_owner.lifetime_elision_allowed = true; } - LifetimeRibKind::Elided(*res) + LifetimeRibKind::elided(*res) } else { LifetimeRibKind::ElisionFailure }; @@ -2540,7 +2502,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())]; for (pat, _) in inputs.clone() { debug!("resolving bindings in pat = {pat:?}"); - self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + self.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| { if let Some(pat) = pat { this.resolve_pattern(pat, PatternSource::FnParam, &mut bindings); } @@ -2966,7 +2928,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ident, ty, expr, define_opaque, eii_impls, .. }) => { self.with_static_rib(def_kind, |this| { - this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Static), |this| { + this.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Static), |this| { this.visit_ty(ty); }); if let Some(expr) = expr { @@ -3004,8 +2966,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { this.visit_generics(generics); this.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Static), - |this| { + LifetimeRibKind::elided(LifetimeRes::Static), + |this: &mut LateResolutionVisitor<'a, 'ast, 'ra, 'tcx>| { if rhs_kind.is_type_const() && !this.r.features.generic_const_parameter_types() { @@ -3040,7 +3002,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { DUMMY_SP, |this| { this.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Infer), + LifetimeRibKind::elided(LifetimeRes::Infer), |this| { this.with_constant_rib( IsRepeatExpr::No, @@ -3401,9 +3363,9 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { generics.span, |this| { this.with_lifetime_rib( - LifetimeRibKind::StaticIfNoLifetimeInScope { - lint_id: item.id, - emit_lint: false, + LifetimeRibKind::Elided { + res: LifetimeRes::Static, + error_in_path: true, }, |this| { this.visit_generics(generics); @@ -3620,67 +3582,47 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { generics.span, |this| { this.with_lifetime_rib( - // Until these are a hard error, we need to create them within the - // correct binder, Otherwise the lifetimes of this assoc const think - // they are lifetimes of the trait. - LifetimeRibKind::AnonymousCreateParameter { - binder: item.id, - report_in_path: true, + LifetimeRibKind::Elided { + res: LifetimeRes::Static, + error_in_path: true, }, |this| { - this.with_lifetime_rib( - LifetimeRibKind::StaticIfNoLifetimeInScope { - lint_id: item.id, - // In impls, it's not a hard error yet due to backcompat. - emit_lint: true, - }, - |this| { - // If this is a trait impl, ensure the const - // exists in trait - this.check_trait_item( - item.id, - *ident, - *ident, - &item.kind, - ValueNS, - item.span, - seen_trait_items, - |i, s, c| ConstNotMemberOfTrait(i, s, c), - ); + // If this is a trait impl, ensure the const + // exists in trait + this.check_trait_item( + item.id, + *ident, + *ident, + &item.kind, + ValueNS, + item.span, + seen_trait_items, + |i, s, c| ConstNotMemberOfTrait(i, s, c), + ); - this.visit_generics(generics); - if rhs_kind.is_type_const() - && !this - .r - .tcx - .features() - .generic_const_parameter_types() - { - this.with_rib(TypeNS, RibKind::ConstParamTy, |this| { - this.with_rib( - ValueNS, - RibKind::ConstParamTy, - |this| { - this.with_lifetime_rib( - LifetimeRibKind::ConstParamTy, - |this| this.visit_ty(ty), - ) - }, - ) - }); - } else { - this.visit_ty(ty); - } - // We allow arbitrary const expressions inside of associated consts, - // even if they are potentially not const evaluatable. - // - // Type parameters can already be used and as associated consts are - // not used as part of the type system, this is far less surprising. - this.resolve_const_item_rhs(rhs_kind, None); - }, - ) + this.visit_generics(generics); + if rhs_kind.is_type_const() + && !this.r.tcx.features().generic_const_parameter_types() + { + this.with_rib(TypeNS, RibKind::ConstParamTy, |this| { + this.with_rib(ValueNS, RibKind::ConstParamTy, |this| { + this.with_lifetime_rib( + LifetimeRibKind::ConstParamTy, + |this| this.visit_ty(ty), + ) + }) + }); + } else { + this.visit_ty(ty); + } + // We allow arbitrary const expressions inside of associated consts, + // even if they are potentially not const evaluatable. + // + // Type parameters can already be used and as associated consts are + // not used as part of the type system, this is far less surprising. + this.resolve_const_item_rhs(rhs_kind, None); }, - ); + ) }, ); self.resolve_define_opaques(define_opaque); @@ -3902,7 +3844,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn resolve_static_body(&mut self, expr: &'ast Expr, item: Option<(Ident, ConstantItemKind)>) { - self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + self.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| { this.with_constant_rib(IsRepeatExpr::No, ConstantHasGenerics::Yes, item, |this| { this.visit_expr(expr) }); @@ -3914,7 +3856,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { rhs_kind: &'ast ConstItemRhsKind, item: Option<(Ident, ConstantItemKind)>, ) { - self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| match rhs_kind { + self.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| match rhs_kind { ConstItemRhsKind::TypeConst { rhs: Some(anon_const) } => { this.resolve_anon_const(anon_const, AnonConstKind::ConstArg(IsRepeatExpr::No)); } @@ -3942,7 +3884,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { // Create lifetimes not with `LifetimeRibKind::Generics` but with `LifetimeRibKind::Elided`, // as we are not processing generic params but generic args in a future call (#156342, #156758). - self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + self.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| { if let Some(qself) = &delegation.qself { this.visit_ty(&qself.ty); } @@ -3977,7 +3919,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { //As we lower target_expr_template body to a body of a function we need a label rib (#148889) this.with_label_rib(RibKind::FnOrCoroutine, |this| { - this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + this.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| { this.visit_block(body); }); }); @@ -3986,7 +3928,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { fn resolve_params(&mut self, params: &'ast [Param]) { let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())]; - self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + self.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| { for Param { pat, .. } in params { this.resolve_pattern(pat, PatternSource::FnParam, &mut bindings); } @@ -5207,7 +5149,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { }; self.with_constant_rib(is_repeat_expr, may_use_generics, None, |this| { - this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + this.with_lifetime_rib(LifetimeRibKind::elided(LifetimeRes::Infer), |this| { resolve_expr(this); }); }); diff --git a/tests/ui/consts/assoc-const-elided-lifetime.rs b/tests/ui/consts/assoc-const-elided-lifetime.rs index 10cd33a8fed59..3fdd725d32a31 100644 --- a/tests/ui/consts/assoc-const-elided-lifetime.rs +++ b/tests/ui/consts/assoc-const-elided-lifetime.rs @@ -1,4 +1,4 @@ -#![deny(elided_lifetimes_in_associated_constant)] +//@ check-pass use std::marker::PhantomData; @@ -8,12 +8,8 @@ struct Foo<'a> { impl<'a> Foo<'a> { const FOO: Foo<'_> = Foo { x: PhantomData::<&()> }; - //~^ ERROR `'_` cannot be used here - //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! const BAR: &() = &(); - //~^ ERROR `&` without an explicit lifetime name cannot be used here - //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! } fn main() {} diff --git a/tests/ui/consts/assoc-const-elided-lifetime.stderr b/tests/ui/consts/assoc-const-elided-lifetime.stderr deleted file mode 100644 index 6277b079bdac7..0000000000000 --- a/tests/ui/consts/assoc-const-elided-lifetime.stderr +++ /dev/null @@ -1,44 +0,0 @@ -error: `'_` cannot be used here - --> $DIR/assoc-const-elided-lifetime.rs:10:20 - | -LL | const FOO: Foo<'_> = Foo { x: PhantomData::<&()> }; - | ^^ - | -note: cannot automatically infer `'static` because of other lifetimes in scope - --> $DIR/assoc-const-elided-lifetime.rs:9:6 - | -LL | impl<'a> Foo<'a> { - | ^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #115010 -note: the lint level is defined here - --> $DIR/assoc-const-elided-lifetime.rs:1:9 - | -LL | #![deny(elided_lifetimes_in_associated_constant)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: use the `'static` lifetime - | -LL - const FOO: Foo<'_> = Foo { x: PhantomData::<&()> }; -LL + const FOO: Foo<'static> = Foo { x: PhantomData::<&()> }; - | - -error: `&` without an explicit lifetime name cannot be used here - --> $DIR/assoc-const-elided-lifetime.rs:14:16 - | -LL | const BAR: &() = &(); - | ^ - | -note: cannot automatically infer `'static` because of other lifetimes in scope - --> $DIR/assoc-const-elided-lifetime.rs:9:6 - | -LL | impl<'a> Foo<'a> { - | ^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #115010 -help: use the `'static` lifetime - | -LL | const BAR: &'static () = &(); - | +++++++ - -error: aborting due to 2 previous errors - diff --git a/tests/ui/consts/static-default-lifetime/elided-lifetime.rs b/tests/ui/consts/static-default-lifetime/elided-lifetime.rs index 989de389180a1..87ca5e082c032 100644 --- a/tests/ui/consts/static-default-lifetime/elided-lifetime.rs +++ b/tests/ui/consts/static-default-lifetime/elided-lifetime.rs @@ -1,11 +1,9 @@ -#![deny(elided_lifetimes_in_associated_constant)] +//@check-pass struct Foo<'a>(&'a ()); impl Foo<'_> { const STATIC: &str = ""; - //~^ ERROR `&` without an explicit lifetime name cannot be used here - //~| WARN this was previously accepted by the compiler but is being phased out } trait Bar { @@ -14,9 +12,6 @@ trait Bar { impl Bar for Foo<'_> { const STATIC: &str = ""; - //~^ ERROR `&` without an explicit lifetime name cannot be used here - //~| WARN this was previously accepted by the compiler but is being phased out - //~| ERROR lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration } fn main() {} diff --git a/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr b/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr deleted file mode 100644 index 370e6655d8607..0000000000000 --- a/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr +++ /dev/null @@ -1,53 +0,0 @@ -error: `&` without an explicit lifetime name cannot be used here - --> $DIR/elided-lifetime.rs:6:19 - | -LL | const STATIC: &str = ""; - | ^ - | -note: cannot automatically infer `'static` because of other lifetimes in scope - --> $DIR/elided-lifetime.rs:5:10 - | -LL | impl Foo<'_> { - | ^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #115010 -note: the lint level is defined here - --> $DIR/elided-lifetime.rs:1:9 - | -LL | #![deny(elided_lifetimes_in_associated_constant)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: use the `'static` lifetime - | -LL | const STATIC: &'static str = ""; - | +++++++ - -error: `&` without an explicit lifetime name cannot be used here - --> $DIR/elided-lifetime.rs:16:19 - | -LL | const STATIC: &str = ""; - | ^ - | -note: cannot automatically infer `'static` because of other lifetimes in scope - --> $DIR/elided-lifetime.rs:15:18 - | -LL | impl Bar for Foo<'_> { - | ^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #115010 -help: use the `'static` lifetime - | -LL | const STATIC: &'static str = ""; - | +++++++ - -error[E0195]: lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration - --> $DIR/elided-lifetime.rs:16:17 - | -LL | const STATIC: &str; - | - lifetimes in impl do not match this associated constant in trait -... -LL | const STATIC: &str = ""; - | ^ lifetimes do not match associated constant in trait - -error: aborting due to 3 previous errors - -For more information about this error, try `rustc --explain E0195`. diff --git a/tests/ui/consts/static-default-lifetime/generic-associated-const.rs b/tests/ui/consts/static-default-lifetime/generic-associated-const.rs index 8fabaa43f5a27..04e7e3d5f7cec 100644 --- a/tests/ui/consts/static-default-lifetime/generic-associated-const.rs +++ b/tests/ui/consts/static-default-lifetime/generic-associated-const.rs @@ -1,18 +1,16 @@ -#![deny(elided_lifetimes_in_associated_constant)] +//@ check-pass + #![feature(generic_const_items)] struct A; impl A { const GAC_TYPE: &str = ""; const GAC_LIFETIME<'a>: &str = ""; - //~^ ERROR `&` without an explicit lifetime name cannot be used here - //~| WARN this was previously accepted by the compiler but is being phased out } trait Trait { const GAC_TYPE: &str = ""; const GAC_LIFETIME<'a>: &str = ""; - //~^ ERROR missing lifetime specifier } fn main() {} diff --git a/tests/ui/consts/static-default-lifetime/generic-associated-const.stderr b/tests/ui/consts/static-default-lifetime/generic-associated-const.stderr deleted file mode 100644 index fe858d685f7fa..0000000000000 --- a/tests/ui/consts/static-default-lifetime/generic-associated-const.stderr +++ /dev/null @@ -1,37 +0,0 @@ -error[E0106]: missing lifetime specifier - --> $DIR/generic-associated-const.rs:14:29 - | -LL | const GAC_LIFETIME<'a>: &str = ""; - | ^ expected named lifetime parameter - | -help: consider using the `'a` lifetime - | -LL | const GAC_LIFETIME<'a>: &'a str = ""; - | ++ - -error: `&` without an explicit lifetime name cannot be used here - --> $DIR/generic-associated-const.rs:7:29 - | -LL | const GAC_LIFETIME<'a>: &str = ""; - | ^ - | -note: cannot automatically infer `'static` because of other lifetimes in scope - --> $DIR/generic-associated-const.rs:7:24 - | -LL | const GAC_LIFETIME<'a>: &str = ""; - | ^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #115010 -note: the lint level is defined here - --> $DIR/generic-associated-const.rs:1:9 - | -LL | #![deny(elided_lifetimes_in_associated_constant)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: use the `'static` lifetime - | -LL | const GAC_LIFETIME<'a>: &'static str = ""; - | +++++++ - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0106`. diff --git a/tests/ui/consts/static-default-lifetime/static-trait-impl.rs b/tests/ui/consts/static-default-lifetime/static-trait-impl.rs index ecc163aecbf1a..d146034114b96 100644 --- a/tests/ui/consts/static-default-lifetime/static-trait-impl.rs +++ b/tests/ui/consts/static-default-lifetime/static-trait-impl.rs @@ -1,4 +1,4 @@ -#![deny(elided_lifetimes_in_associated_constant)] +//@ check-pass trait Bar<'a> { const STATIC: &'a str; @@ -7,9 +7,6 @@ trait Bar<'a> { struct A; impl Bar<'_> for A { const STATIC: &str = ""; - //~^ ERROR `&` without an explicit lifetime name cannot be used here - //~| WARN this was previously accepted by the compiler but is being phased out - //~| ERROR lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration } struct B; @@ -19,12 +16,10 @@ impl Bar<'static> for B { struct C; impl Bar<'_> for C { - // make ^^ not cause const STATIC: &'static str = { struct B; impl Bar<'static> for B { const STATIC: &str = ""; - // ^ to emit a future incompat warning } "" }; diff --git a/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr b/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr deleted file mode 100644 index ab82515162014..0000000000000 --- a/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr +++ /dev/null @@ -1,35 +0,0 @@ -error: `&` without an explicit lifetime name cannot be used here - --> $DIR/static-trait-impl.rs:9:19 - | -LL | const STATIC: &str = ""; - | ^ - | -note: cannot automatically infer `'static` because of other lifetimes in scope - --> $DIR/static-trait-impl.rs:8:10 - | -LL | impl Bar<'_> for A { - | ^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #115010 -note: the lint level is defined here - --> $DIR/static-trait-impl.rs:1:9 - | -LL | #![deny(elided_lifetimes_in_associated_constant)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: use the `'static` lifetime - | -LL | const STATIC: &'static str = ""; - | +++++++ - -error[E0195]: lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration - --> $DIR/static-trait-impl.rs:9:17 - | -LL | const STATIC: &'a str; - | - lifetimes in impl do not match this associated constant in trait -... -LL | const STATIC: &str = ""; - | ^ lifetimes do not match associated constant in trait - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0195`. diff --git a/tests/ui/suggestions/missing-lifetime-in-assoc-const-type.rs b/tests/ui/suggestions/missing-lifetime-in-assoc-const-type.rs index a60f0b94587f8..7745ebaceb9aa 100644 --- a/tests/ui/suggestions/missing-lifetime-in-assoc-const-type.rs +++ b/tests/ui/suggestions/missing-lifetime-in-assoc-const-type.rs @@ -9,6 +9,11 @@ trait ZstAssert: Sized { const D: T = T { a: &(), b: &() }; //~ ERROR implicit elided lifetime not allowed here } +const A: &str = ""; +const B: S = S { s: &() }; +const C: &'_ str = ""; +const D: T = T { a: &(), b: &() }; + struct S<'a> { s: &'a (), } From 23cf412f06e4c1f0f7d18118ef13c301f249b168 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 8 Jul 2026 19:12:18 +0000 Subject: [PATCH 77/78] Allow BackwardIncompatibleDropHint in polonius legacy * Allow BackwardIncompatibleDropHint in polonius legacy * Move test file to be under polonius * Remove resolved crash test file --- .../rustc_borrowck/src/polonius/legacy/loan_invalidations.rs | 5 +++-- .../nll/polonius/allow-backward-incompatible-drop-hint.rs} | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) rename tests/{crashes/157568.rs => ui/nll/polonius/allow-backward-incompatible-drop-hint.rs} (85%) diff --git a/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs b/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs index 9edf7103f3990..64ba106b68adc 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs @@ -70,7 +70,9 @@ impl<'a, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'a, 'tcx> { // Doesn't have any language semantics | StatementKind::Coverage(..) // Does not actually affect borrowck - | StatementKind::StorageLive(..) => {} + | StatementKind::StorageLive(..) + // Does not affect borrowck + | StatementKind::BackwardIncompatibleDropHint { .. } => {} StatementKind::StorageDead(local) => { self.access_place( location, @@ -81,7 +83,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'a, 'tcx> { } StatementKind::ConstEvalCounter | StatementKind::Nop - | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::SetDiscriminant { .. } => { bug!("Statement not allowed in this MIR phase") } diff --git a/tests/crashes/157568.rs b/tests/ui/nll/polonius/allow-backward-incompatible-drop-hint.rs similarity index 85% rename from tests/crashes/157568.rs rename to tests/ui/nll/polonius/allow-backward-incompatible-drop-hint.rs index 627bfbadf6bc4..b2a9f648809ff 100644 --- a/tests/crashes/157568.rs +++ b/tests/ui/nll/polonius/allow-backward-incompatible-drop-hint.rs @@ -1,5 +1,7 @@ -//@ known-bug: #157568 +// Test for issue #157568 //@ compile-flags: -Zpolonius +//@ check-pass + #![warn(rust_2024_compatibility)] pub struct F; impl std::fmt::Debug for F { @@ -7,4 +9,5 @@ impl std::fmt::Debug for F { f.debug_struct("F").finish_non_exhaustive() } } + fn main() {} From e7d2e3d37d1a1c4842d673e40c82d10901b17daf Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 9 Jul 2026 15:39:35 -0400 Subject: [PATCH 78/78] bootstrap: expand '@argfile' arguments to rustc shim When cargo invokes `rustc`, if the command line exceeds some system defined limit, `cargo` will try again and pass the arguments in an argfile. When the bootstrap rustc shim tries to detect `--target`, it does not account for `@argfile` arguments. As a result, the shim doesn't find `--target` and incorrectly sets `is_build_script` to true, as well as passing the `RUSTC_HOST_FLAGS` to the underlying compiler This PR makes the shim read all the arguments from every `@argfile`. Previous work already updated the shim to use `ArgFileCommand` to pass arguments down to the underlying `rustc`. --- src/bootstrap/src/bin/rustc.rs | 6 ++--- src/bootstrap/src/bin/rustdoc.rs | 4 +-- src/bootstrap/src/utils/shared_helpers.rs | 30 ++++++++++++++++++++++- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/bootstrap/src/bin/rustc.rs b/src/bootstrap/src/bin/rustc.rs index ca25c2b132206..c8aff10b6355b 100644 --- a/src/bootstrap/src/bin/rustc.rs +++ b/src/bootstrap/src/bin/rustc.rs @@ -22,8 +22,8 @@ use std::time::Instant; use arg_file_command::ArgFileCommand; use shared_helpers::{ - dylib_path, dylib_path_var, exe, maybe_dump, parse_rustc_stage, parse_rustc_verbose, - parse_value_from_args, + collect_args, dylib_path, dylib_path_var, exe, maybe_dump, parse_rustc_stage, + parse_rustc_verbose, parse_value_from_args, }; #[path = "../utils/shared_helpers.rs"] @@ -36,7 +36,7 @@ mod arg_file_command; mod proc_macro_deps; fn main() { - let orig_args = env::args_os().skip(1).collect::>(); + let orig_args = collect_args(); let mut args = orig_args.clone(); let stage = parse_rustc_stage(); diff --git a/src/bootstrap/src/bin/rustdoc.rs b/src/bootstrap/src/bin/rustdoc.rs index ef2462af952e1..fd048d138e090 100644 --- a/src/bootstrap/src/bin/rustdoc.rs +++ b/src/bootstrap/src/bin/rustdoc.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use arg_file_command::ArgFileCommand; use shared_helpers::{ - dylib_path, dylib_path_var, maybe_dump, parse_rustc_stage, parse_rustc_verbose, + collect_args, dylib_path, dylib_path_var, maybe_dump, parse_rustc_stage, parse_rustc_verbose, parse_value_from_args, }; @@ -18,7 +18,7 @@ mod shared_helpers; mod arg_file_command; fn main() { - let args = env::args_os().skip(1).collect::>(); + let args = collect_args(); let stage = parse_rustc_stage(); let verbose = parse_rustc_verbose(); diff --git a/src/bootstrap/src/utils/shared_helpers.rs b/src/bootstrap/src/utils/shared_helpers.rs index d620cc4bbb6b4..78af70ddb0f42 100644 --- a/src/bootstrap/src/utils/shared_helpers.rs +++ b/src/bootstrap/src/utils/shared_helpers.rs @@ -16,7 +16,8 @@ use std::env; use std::ffi::OsString; use std::fs::OpenOptions; -use std::io::Write; +use std::io::{BufRead, Write}; +use std::path::Path; use std::process::Command; use std::str::FromStr; @@ -126,3 +127,30 @@ pub fn parse_value_from_args<'a>(args: &'a [OsString], key: &str) -> Option<&'a None } + +/// Collect all the command line arguments, including the arguments from any `@argfile` +pub fn collect_args() -> Vec { + let mut args = Vec::with_capacity(env::args_os().len()); + for arg in env::args_os().skip(1) { + if let Some(s) = arg.to_str() + && let Some(path) = s.strip_prefix('@') + { + args.extend(args_from_argfile(Path::new(path))); + } else { + args.push(arg) + } + } + args +} + +/// Reads all the arguments from argfile given by `path`. +/// Each argument should be on a line by itself +fn args_from_argfile(path: &Path) -> Vec { + fn collect_lines(path: &Path) -> Result, std::io::Error> { + let file = std::fs::File::open(path)?; + let lines: Result, std::io::Error> = + std::io::BufReader::new(file).lines().map(|r| r.map(OsString::from)).collect(); + lines + } + collect_lines(path).expect("read args from argfile {path:?}") +}