From 690a8a6dfd7a0bff4482826a0af885c3690cb0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 16 Oct 2025 19:53:29 +0200 Subject: [PATCH 01/11] Tweak diagnostics for relaxed bounds in invalid positions --- compiler/rustc_ast_lowering/src/lib.rs | 46 +++++++++---------- .../feature-gate-more-maybe-bounds.stderr | 2 +- .../parser/trait-object-trait-parens.stderr | 6 +++ .../sized-hierarchy/default-supertrait.stderr | 2 +- .../ui/trait-bounds/more_maybe_bounds.stderr | 6 +-- .../traits/wf-object/only-maybe-bound.stderr | 2 + .../relaxed-bounds-invalid-places.stderr | 20 +++++--- 7 files changed, 49 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4e2243e87873c..5968596dacceb 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2103,33 +2103,33 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } } RelaxedBoundPolicy::Forbidden(reason) => { + let gate = |context, subject| { + if self.tcx.features().more_maybe_bounds() { + return; + } + + let mut diag = self.dcx().struct_span_err( + span, + format!("relaxed bounds are not permitted in {context}"), + ); + if let Some(def_id) = trait_ref.trait_def_id() + && self.tcx.is_lang_item(def_id, hir::LangItem::Sized) + { + diag.note(format!( + "{subject} are not implicitly bounded by `Sized`, \ + so there is nothing to relax" + )); + } + diag.emit(); + }; + match reason { RelaxedBoundForbiddenReason::TraitObjectTy => { - if self.tcx.features().more_maybe_bounds() { - return; - } - - self.dcx().span_err( - span, - "relaxed bounds are not permitted in trait object types", - ); + gate("trait object types", "trait object types"); return; } RelaxedBoundForbiddenReason::SuperTrait => { - if self.tcx.features().more_maybe_bounds() { - return; - } - - let mut diag = self.dcx().struct_span_err( - span, - "relaxed bounds are not permitted in supertrait bounds", - ); - if let Some(def_id) = trait_ref.trait_def_id() - && self.tcx.is_lang_item(def_id, hir::LangItem::Sized) - { - diag.note("traits are `?Sized` by default"); - } - diag.emit(); + gate("supertrait bounds", "traits"); return; } RelaxedBoundForbiddenReason::AssocTyBounds @@ -2142,7 +2142,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { .struct_span_err(span, "this relaxed bound is not permitted here") .with_note( "in this context, relaxed bounds are only allowed on \ - type parameters defined by the closest item", + type parameters defined on the closest item", ) .emit(); } diff --git a/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr b/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr index da6ad5f16e20c..092655b0f53a6 100644 --- a/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr +++ b/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr @@ -10,7 +10,7 @@ error: this relaxed bound is not permitted here LL | trait Trait4 where Self: ?Trait1 {} | ^^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: relaxed bounds are not permitted in trait object types --> $DIR/feature-gate-more-maybe-bounds.rs:8:28 diff --git a/tests/ui/parser/trait-object-trait-parens.stderr b/tests/ui/parser/trait-object-trait-parens.stderr index f498d7d36bba6..c43ca23c4885d 100644 --- a/tests/ui/parser/trait-object-trait-parens.stderr +++ b/tests/ui/parser/trait-object-trait-parens.stderr @@ -3,18 +3,24 @@ error: relaxed bounds are not permitted in trait object types | LL | let _: Box<(Obj) + (?Sized) + (for<'a> Trait<'a>)>; | ^^^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types --> $DIR/trait-object-trait-parens.rs:13:16 | LL | let _: Box Trait<'a>) + (Obj)>; | ^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types --> $DIR/trait-object-trait-parens.rs:18:44 | LL | let _: Box Trait<'a> + (Obj) + (?Sized)>; | ^^^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax warning: trait objects without an explicit `dyn` are deprecated --> $DIR/trait-object-trait-parens.rs:8:16 diff --git a/tests/ui/sized-hierarchy/default-supertrait.stderr b/tests/ui/sized-hierarchy/default-supertrait.stderr index f5589d6e279cc..a4ade7fb83941 100644 --- a/tests/ui/sized-hierarchy/default-supertrait.stderr +++ b/tests/ui/sized-hierarchy/default-supertrait.stderr @@ -4,7 +4,7 @@ error: relaxed bounds are not permitted in supertrait bounds LL | trait NegSized: ?Sized { } | ^^^^^^ | - = note: traits are `?Sized` by default + = note: traits are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in supertrait bounds --> $DIR/default-supertrait.rs:13:21 diff --git a/tests/ui/trait-bounds/more_maybe_bounds.stderr b/tests/ui/trait-bounds/more_maybe_bounds.stderr index 0d78cfd582047..b8782c560ad83 100644 --- a/tests/ui/trait-bounds/more_maybe_bounds.stderr +++ b/tests/ui/trait-bounds/more_maybe_bounds.stderr @@ -4,7 +4,7 @@ error: this relaxed bound is not permitted here LL | fn baz() where T: Iterator {} | ^^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/more_maybe_bounds.rs:29:21 @@ -12,7 +12,7 @@ error: this relaxed bound is not permitted here LL | fn f() where T: ?Trait1 {} | ^^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/more_maybe_bounds.rs:35:34 @@ -20,7 +20,7 @@ error: this relaxed bound is not permitted here LL | struct S2(T) where for<'a> T: ?Trait5<'a>; | ^^^^^^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: bound modifier `?` can only be applied to default traits like `Sized` --> $DIR/more_maybe_bounds.rs:17:20 diff --git a/tests/ui/traits/wf-object/only-maybe-bound.stderr b/tests/ui/traits/wf-object/only-maybe-bound.stderr index 6ae4568c699a7..3f02d5a8a0c85 100644 --- a/tests/ui/traits/wf-object/only-maybe-bound.stderr +++ b/tests/ui/traits/wf-object/only-maybe-bound.stderr @@ -3,6 +3,8 @@ error: relaxed bounds are not permitted in trait object types | LL | type _0 = dyn ?Sized; | ^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error[E0224]: at least one trait is required for an object type --> $DIR/only-maybe-bound.rs:3:11 diff --git a/tests/ui/unsized/relaxed-bounds-invalid-places.stderr b/tests/ui/unsized/relaxed-bounds-invalid-places.stderr index d3f0535e2f0cb..34f5cebeeaf1a 100644 --- a/tests/ui/unsized/relaxed-bounds-invalid-places.stderr +++ b/tests/ui/unsized/relaxed-bounds-invalid-places.stderr @@ -4,7 +4,7 @@ error: this relaxed bound is not permitted here LL | struct S1(T) where (T): ?Sized; | ^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/relaxed-bounds-invalid-places.rs:8:27 @@ -12,7 +12,7 @@ error: this relaxed bound is not permitted here LL | struct S2(T) where u8: ?Sized; | ^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/relaxed-bounds-invalid-places.rs:10:35 @@ -20,7 +20,7 @@ error: this relaxed bound is not permitted here LL | struct S3(T) where &'static T: ?Sized; | ^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/relaxed-bounds-invalid-places.rs:14:34 @@ -28,7 +28,7 @@ error: this relaxed bound is not permitted here LL | struct S4(T) where for<'a> T: ?Trait<'a>; | ^^^^^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/relaxed-bounds-invalid-places.rs:22:21 @@ -36,7 +36,7 @@ error: this relaxed bound is not permitted here LL | fn f() where T: ?Sized {} | ^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here --> $DIR/relaxed-bounds-invalid-places.rs:27:41 @@ -44,7 +44,7 @@ error: this relaxed bound is not permitted here LL | struct S6(T) where T: Iterator; | ^^^^^^ | - = note: in this context, relaxed bounds are only allowed on type parameters defined by the closest item + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: relaxed bounds are not permitted in supertrait bounds --> $DIR/relaxed-bounds-invalid-places.rs:29:11 @@ -52,25 +52,31 @@ error: relaxed bounds are not permitted in supertrait bounds LL | trait Tr: ?Sized {} | ^^^^^^ | - = note: traits are `?Sized` by default + = note: traits are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types --> $DIR/relaxed-bounds-invalid-places.rs:33:20 | LL | type O1 = dyn Tr + ?Sized; | ^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types --> $DIR/relaxed-bounds-invalid-places.rs:34:15 | LL | type O2 = dyn ?Sized + ?Sized + Tr; | ^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types --> $DIR/relaxed-bounds-invalid-places.rs:34:24 | LL | type O2 = dyn ?Sized + ?Sized + Tr; | ^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: bound modifier `?` can only be applied to `Sized` --> $DIR/relaxed-bounds-invalid-places.rs:14:34 From ce68cd3762fef8eb799b5b75ba07eb82597de44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 16 Oct 2025 16:14:39 +0200 Subject: [PATCH 02/11] Reject relaxed bounds inside trait alias bounds --- compiler/rustc_ast_lowering/src/item.rs | 2 +- compiler/rustc_ast_lowering/src/lib.rs | 5 ++++ .../src/hir_ty_lowering/bounds.rs | 4 +-- tests/rustdoc-ui/doc-alias-assoc-const.rs | 2 -- tests/rustdoc-ui/doc-alias-assoc-const.stderr | 2 +- tests/ui/sized-hierarchy/trait-aliases.rs | 9 ------ .../effectively-empty-trait-object-type.rs | 20 +++++++++++++ ...effectively-empty-trait-object-type.stderr | 21 ++++++++++++++ tests/ui/traits/alias/empty.rs | 17 +++++++++++ tests/ui/traits/alias/maybe-bound.rs | 29 ------------------- tests/ui/traits/alias/only-maybe-bound.rs | 22 -------------- tests/ui/traits/alias/only-maybe-bound.stderr | 21 -------------- tests/ui/traits/alias/relaxed-bounds.rs | 19 ++++++++++++ tests/ui/traits/alias/relaxed-bounds.stderr | 24 +++++++++++++++ tests/ui/traits/wf-object/only-maybe-bound.rs | 7 ----- .../traits/wf-object/only-maybe-bound.stderr | 17 ----------- .../unsized/relaxed-bounds-invalid-places.rs | 3 ++ .../relaxed-bounds-invalid-places.stderr | 23 ++++++++++++--- 18 files changed, 131 insertions(+), 116 deletions(-) delete mode 100644 tests/ui/sized-hierarchy/trait-aliases.rs create mode 100644 tests/ui/traits/alias/effectively-empty-trait-object-type.rs create mode 100644 tests/ui/traits/alias/effectively-empty-trait-object-type.stderr create mode 100644 tests/ui/traits/alias/empty.rs delete mode 100644 tests/ui/traits/alias/maybe-bound.rs delete mode 100644 tests/ui/traits/alias/only-maybe-bound.rs delete mode 100644 tests/ui/traits/alias/only-maybe-bound.stderr create mode 100644 tests/ui/traits/alias/relaxed-bounds.rs create mode 100644 tests/ui/traits/alias/relaxed-bounds.stderr delete mode 100644 tests/ui/traits/wf-object/only-maybe-bound.rs delete mode 100644 tests/ui/traits/wf-object/only-maybe-bound.stderr diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 53351f91c46bc..1a52ed02c4042 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -426,7 +426,7 @@ impl<'hir> LoweringContext<'_, 'hir> { |this| { this.lower_param_bounds( bounds, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::TraitAlias), ImplTraitContext::Disallowed(ImplTraitPosition::Bound), ) }, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 5968596dacceb..aaca9f4547e9b 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -296,6 +296,7 @@ enum RelaxedBoundPolicy<'a> { enum RelaxedBoundForbiddenReason { TraitObjectTy, SuperTrait, + TraitAlias, AssocTyBounds, LateBoundVarsInScope, } @@ -2132,6 +2133,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { gate("supertrait bounds", "traits"); return; } + RelaxedBoundForbiddenReason::TraitAlias => { + gate("trait alias bounds", "trait aliases"); + return; + } RelaxedBoundForbiddenReason::AssocTyBounds | RelaxedBoundForbiddenReason::LateBoundVarsInScope => {} }; diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 7accab8df87c7..b6c4ca3b95949 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -202,9 +202,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // where we are guaranteed to catch *all* bounds like in // `Self::lower_poly_trait_ref`. List of concrete issues: // FIXME(more_maybe_bounds): We don't call this for trait object tys, supertrait - // bounds or associated type bounds (ATB)! - // FIXME(trait_alias, #143122): We don't call it for the RHS. Arguably however, - // AST lowering should reject them outright. + // bounds, trait alias bounds, assoc type bounds (ATB)! let bounds = collect_relaxed_bounds(hir_bounds, self_ty_where_predicates); self.check_and_report_invalid_relaxed_bounds(bounds); } diff --git a/tests/rustdoc-ui/doc-alias-assoc-const.rs b/tests/rustdoc-ui/doc-alias-assoc-const.rs index d95324734be47..cbbc3fe4e21f3 100644 --- a/tests/rustdoc-ui/doc-alias-assoc-const.rs +++ b/tests/rustdoc-ui/doc-alias-assoc-const.rs @@ -1,5 +1,3 @@ -#![feature(trait_alias)] - pub struct Foo; pub trait Bar { diff --git a/tests/rustdoc-ui/doc-alias-assoc-const.stderr b/tests/rustdoc-ui/doc-alias-assoc-const.stderr index cd3ad4ab393a5..cc628c39400b1 100644 --- a/tests/rustdoc-ui/doc-alias-assoc-const.stderr +++ b/tests/rustdoc-ui/doc-alias-assoc-const.stderr @@ -1,5 +1,5 @@ error: `#[doc(alias = "...")]` isn't allowed on associated constant in trait implementation block - --> $DIR/doc-alias-assoc-const.rs:10:11 + --> $DIR/doc-alias-assoc-const.rs:8:11 | LL | #[doc(alias = "CONST_BAZ")] | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/sized-hierarchy/trait-aliases.rs b/tests/ui/sized-hierarchy/trait-aliases.rs deleted file mode 100644 index ffec302adaa5c..0000000000000 --- a/tests/ui/sized-hierarchy/trait-aliases.rs +++ /dev/null @@ -1,9 +0,0 @@ -//@ check-pass -//@ compile-flags: --crate-type=lib -#![feature(trait_alias)] - -// Checks that `?Sized` in a trait alias doesn't trigger an ICE. - -use std::ops::{Index, IndexMut}; - -pub trait SlicePrereq = ?Sized + IndexMut>::Output>; diff --git a/tests/ui/traits/alias/effectively-empty-trait-object-type.rs b/tests/ui/traits/alias/effectively-empty-trait-object-type.rs new file mode 100644 index 0000000000000..f7e8daa88e7c2 --- /dev/null +++ b/tests/ui/traits/alias/effectively-empty-trait-object-type.rs @@ -0,0 +1,20 @@ +// Test that we reject trait object types that effectively (i.e., after trait alias expansion) +// don't contain any bounds. + +#![feature(trait_alias)] + +trait Empty0 =; + +// Nest a couple of levels deep: +trait Empty1 = Empty0; +trait Empty2 = Empty1; + +// Straight list expansion: +type Type0 = dyn Empty2; //~ ERROR at least one trait is required for an object type [E0224] + +// Twice: +trait Empty3 = Empty2 + Empty2; + +type Type1 = dyn Empty3; //~ ERROR at least one trait is required for an object type [E0224] + +fn main() {} diff --git a/tests/ui/traits/alias/effectively-empty-trait-object-type.stderr b/tests/ui/traits/alias/effectively-empty-trait-object-type.stderr new file mode 100644 index 0000000000000..dbef9c3b5b31b --- /dev/null +++ b/tests/ui/traits/alias/effectively-empty-trait-object-type.stderr @@ -0,0 +1,21 @@ +error[E0224]: at least one trait is required for an object type + --> $DIR/effectively-empty-trait-object-type.rs:13:14 + | +LL | trait Empty2 = Empty1; + | ------------ this alias does not contain a trait +... +LL | type Type0 = dyn Empty2; + | ^^^^^^^^^^ + +error[E0224]: at least one trait is required for an object type + --> $DIR/effectively-empty-trait-object-type.rs:18:14 + | +LL | trait Empty3 = Empty2 + Empty2; + | ------------ this alias does not contain a trait +LL | +LL | type Type1 = dyn Empty3; + | ^^^^^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0224`. diff --git a/tests/ui/traits/alias/empty.rs b/tests/ui/traits/alias/empty.rs new file mode 100644 index 0000000000000..68d642f485395 --- /dev/null +++ b/tests/ui/traits/alias/empty.rs @@ -0,0 +1,17 @@ +// Ensure that there are straightforward ways to define "empty" / "trivial" / "unit" trait aliases +// which don't impose any constraints when used as a bound (since they expand to nothing). +//@ check-pass +#![feature(trait_alias)] + +trait Empty =; + +trait Trivial = where; + +trait Unit = where Self:; + +fn check() {} + +fn main() { + check::<()>(); // OK. "`(): Empty`" is trivially satisfied + check::(); // OK. `Empty` is truly empty and isn't implicitly bounded by `Sized`. +} diff --git a/tests/ui/traits/alias/maybe-bound.rs b/tests/ui/traits/alias/maybe-bound.rs deleted file mode 100644 index 9fdeb714d5e75..0000000000000 --- a/tests/ui/traits/alias/maybe-bound.rs +++ /dev/null @@ -1,29 +0,0 @@ -//@ build-pass (FIXME(62277): could be check-pass?) - -// Test that `dyn ... + ?Sized + ...` resulting from the expansion of trait aliases is okay. - -#![feature(trait_alias)] - -trait Foo {} - -trait S = ?Sized; - -// Nest a couple of levels deep: -trait _0 = S; -trait _1 = _0; - -// Straight list expansion: -type _T0 = dyn _1 + Foo; - -// In second position: -type _T1 = dyn Foo + _1; - -// ... and with an auto trait: -type _T2 = dyn Foo + Send + _1; - -// Twice: -trait _2 = _1 + _1; - -type _T3 = dyn _2 + Foo; - -fn main() {} diff --git a/tests/ui/traits/alias/only-maybe-bound.rs b/tests/ui/traits/alias/only-maybe-bound.rs deleted file mode 100644 index e4abf314e0a96..0000000000000 --- a/tests/ui/traits/alias/only-maybe-bound.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `dyn ?Sized` (i.e., a trait object with only a maybe buond) is not allowed, when just -// `?Sized` results from trait alias expansion. - -#![feature(trait_alias)] - -trait S = ?Sized; - -// Nest a couple of levels deep: -trait _0 = S; -trait _1 = _0; - -// Straight list expansion: -type _T0 = dyn _1; -//~^ ERROR at least one trait is required for an object type [E0224] - -// Twice: -trait _2 = _1 + _1; - -type _T1 = dyn _2; -//~^ ERROR at least one trait is required for an object type [E0224] - -fn main() {} diff --git a/tests/ui/traits/alias/only-maybe-bound.stderr b/tests/ui/traits/alias/only-maybe-bound.stderr deleted file mode 100644 index 175ec8120ecc6..0000000000000 --- a/tests/ui/traits/alias/only-maybe-bound.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error[E0224]: at least one trait is required for an object type - --> $DIR/only-maybe-bound.rs:13:12 - | -LL | trait _1 = _0; - | -------- this alias does not contain a trait -... -LL | type _T0 = dyn _1; - | ^^^^^^ - -error[E0224]: at least one trait is required for an object type - --> $DIR/only-maybe-bound.rs:19:12 - | -LL | trait _2 = _1 + _1; - | -------- this alias does not contain a trait -LL | -LL | type _T1 = dyn _2; - | ^^^^^^ - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0224`. diff --git a/tests/ui/traits/alias/relaxed-bounds.rs b/tests/ui/traits/alias/relaxed-bounds.rs new file mode 100644 index 0000000000000..88e092c631622 --- /dev/null +++ b/tests/ui/traits/alias/relaxed-bounds.rs @@ -0,0 +1,19 @@ +#![feature(trait_alias)] + +// Ensure that relaxed bounds are not permitted in the `Self` bounds of trait aliases because trait +// aliases (like traits) aren't implicitly bounded by `Sized` so there's nothing to relax. + +trait Alias0 = ?Sized; //~ ERROR relaxed bounds are not permitted in trait alias bounds +trait Alias1 = where Self: ?Sized; //~ ERROR this relaxed bound is not permitted here + +trait Alias2 =; // OK +trait Alias3 = where T: ?Sized; // OK + +// Make sure that we don't permit "relaxing" trait aliases since we don't want to expand trait +// aliases during sized elaboration for simplicity as we'd need to handle relaxing arbitrary bounds +// (e.g., ones with modifiers, outlives-bounds, …) and where-clauses. + +trait SizedAlias = Sized; +fn take() {} //~ ERROR bound modifier `?` can only be applied to `Sized` + +fn main() {} diff --git a/tests/ui/traits/alias/relaxed-bounds.stderr b/tests/ui/traits/alias/relaxed-bounds.stderr new file mode 100644 index 0000000000000..d7782b723a305 --- /dev/null +++ b/tests/ui/traits/alias/relaxed-bounds.stderr @@ -0,0 +1,24 @@ +error: relaxed bounds are not permitted in trait alias bounds + --> $DIR/relaxed-bounds.rs:6:16 + | +LL | trait Alias0 = ?Sized; + | ^^^^^^ + | + = note: trait aliases are not implicitly bounded by `Sized`, so there is nothing to relax + +error: this relaxed bound is not permitted here + --> $DIR/relaxed-bounds.rs:7:28 + | +LL | trait Alias1 = where Self: ?Sized; + | ^^^^^^ + | + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/relaxed-bounds.rs:17:12 + | +LL | fn take() {} + | ^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/traits/wf-object/only-maybe-bound.rs b/tests/ui/traits/wf-object/only-maybe-bound.rs deleted file mode 100644 index 96360e0331cdb..0000000000000 --- a/tests/ui/traits/wf-object/only-maybe-bound.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Test that `dyn ?Sized` (i.e., a trait object with only a maybe buond) is not allowed. - -type _0 = dyn ?Sized; -//~^ ERROR at least one trait is required for an object type [E0224] -//~| ERROR relaxed bounds are not permitted in trait object types - -fn main() {} diff --git a/tests/ui/traits/wf-object/only-maybe-bound.stderr b/tests/ui/traits/wf-object/only-maybe-bound.stderr deleted file mode 100644 index 3f02d5a8a0c85..0000000000000 --- a/tests/ui/traits/wf-object/only-maybe-bound.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: relaxed bounds are not permitted in trait object types - --> $DIR/only-maybe-bound.rs:3:15 - | -LL | type _0 = dyn ?Sized; - | ^^^^^^ - | - = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax - -error[E0224]: at least one trait is required for an object type - --> $DIR/only-maybe-bound.rs:3:11 - | -LL | type _0 = dyn ?Sized; - | ^^^^^^^^^^ - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0224`. diff --git a/tests/ui/unsized/relaxed-bounds-invalid-places.rs b/tests/ui/unsized/relaxed-bounds-invalid-places.rs index 4c1f242a01cef..5aa1dbb9a094e 100644 --- a/tests/ui/unsized/relaxed-bounds-invalid-places.rs +++ b/tests/ui/unsized/relaxed-bounds-invalid-places.rs @@ -30,6 +30,9 @@ trait Tr: ?Sized {} //~ ERROR relaxed bounds are not permitted in supertrait bou // Test that relaxed `Sized` bounds are rejected in trait object types: +type O0 = dyn ?Sized; +//~^ ERROR relaxed bounds are not permitted in trait object types +//~| ERROR at least one trait is required for an object type [E0224] type O1 = dyn Tr + ?Sized; //~ ERROR relaxed bounds are not permitted in trait object types type O2 = dyn ?Sized + ?Sized + Tr; //~^ ERROR relaxed bounds are not permitted in trait object types diff --git a/tests/ui/unsized/relaxed-bounds-invalid-places.stderr b/tests/ui/unsized/relaxed-bounds-invalid-places.stderr index 34f5cebeeaf1a..7f54d045a1ce8 100644 --- a/tests/ui/unsized/relaxed-bounds-invalid-places.stderr +++ b/tests/ui/unsized/relaxed-bounds-invalid-places.stderr @@ -55,7 +55,15 @@ LL | trait Tr: ?Sized {} = note: traits are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types - --> $DIR/relaxed-bounds-invalid-places.rs:33:20 + --> $DIR/relaxed-bounds-invalid-places.rs:33:15 + | +LL | type O0 = dyn ?Sized; + | ^^^^^^ + | + = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax + +error: relaxed bounds are not permitted in trait object types + --> $DIR/relaxed-bounds-invalid-places.rs:36:20 | LL | type O1 = dyn Tr + ?Sized; | ^^^^^^ @@ -63,7 +71,7 @@ LL | type O1 = dyn Tr + ?Sized; = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types - --> $DIR/relaxed-bounds-invalid-places.rs:34:15 + --> $DIR/relaxed-bounds-invalid-places.rs:37:15 | LL | type O2 = dyn ?Sized + ?Sized + Tr; | ^^^^^^ @@ -71,7 +79,7 @@ LL | type O2 = dyn ?Sized + ?Sized + Tr; = note: trait object types are not implicitly bounded by `Sized`, so there is nothing to relax error: relaxed bounds are not permitted in trait object types - --> $DIR/relaxed-bounds-invalid-places.rs:34:24 + --> $DIR/relaxed-bounds-invalid-places.rs:37:24 | LL | type O2 = dyn ?Sized + ?Sized + Tr; | ^^^^^^ @@ -90,5 +98,12 @@ error: bound modifier `?` can only be applied to `Sized` LL | struct S5(*const T) where T: ?Trait<'static> + ?Sized; | ^^^^^^^^^^^^^^^ -error: aborting due to 12 previous errors +error[E0224]: at least one trait is required for an object type + --> $DIR/relaxed-bounds-invalid-places.rs:33:11 + | +LL | type O0 = dyn ?Sized; + | ^^^^^^^^^^ + +error: aborting due to 14 previous errors +For more information about this error, try `rustc --explain E0224`. From aee9d3b4c0835a4562bb8d66a5d6c1728ad7d906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 15 Oct 2025 18:40:19 +0200 Subject: [PATCH 03/11] Don't permit `?Sized` in more places just because `more_maybe_bounds` was enabled The internal feature `more_maybe_bounds` doesn't influence sized elaboration in HIR ty lowering and therefore doesn't get to dictate where `?Sized` is allowed. --- compiler/rustc_ast_lowering/src/lib.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index aaca9f4547e9b..0a4d39b175be7 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2086,12 +2086,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { span: Span, rbp: RelaxedBoundPolicy<'_>, ) { - // Even though feature `more_maybe_bounds` bypasses the given policy and (currently) enables - // relaxed bounds in every conceivable position[^1], we don't want to advertise it to the user - // (via a feature gate) since it's super internal. Besides this, it'd be quite distracting. + // Even though feature `more_maybe_bounds` enables the user to relax all default bounds + // other than `Sized` in a lot more positions (thereby bypassing the given policy), we don't + // want to advertise it to the user (via a feature gate error) since it's super internal. // - // [^1]: Strictly speaking, this is incorrect (at the very least for `Sized`) because it's - // no longer fully consistent with default trait elaboration in HIR ty lowering. + // FIXME(more_maybe_bounds): Moreover, if we actually were to add proper default traits + // (like a hypothetical `Move` or `Leak`) we would want to validate the location according + // to default trait elaboration in HIR ty lowering (which depends on the specific trait in + // question: E.g., `?Sized` & `?Move` most likely won't be allowed in all the same places). match rbp { RelaxedBoundPolicy::Allowed => return, @@ -2105,17 +2107,21 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } RelaxedBoundPolicy::Forbidden(reason) => { let gate = |context, subject| { - if self.tcx.features().more_maybe_bounds() { + let extended = self.tcx.features().more_maybe_bounds(); + let is_sized = trait_ref + .trait_def_id() + .is_some_and(|def_id| self.tcx.is_lang_item(def_id, hir::LangItem::Sized)); + + if extended && !is_sized { return; } + let prefix = if extended { "`Sized` " } else { "" }; let mut diag = self.dcx().struct_span_err( span, - format!("relaxed bounds are not permitted in {context}"), + format!("relaxed {prefix}bounds are not permitted in {context}"), ); - if let Some(def_id) = trait_ref.trait_def_id() - && self.tcx.is_lang_item(def_id, hir::LangItem::Sized) - { + if is_sized { diag.note(format!( "{subject} are not implicitly bounded by `Sized`, \ so there is nothing to relax" From 03dfb84ee118ea69774208a080f35c20850fbc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 15 Oct 2025 19:43:40 +0200 Subject: [PATCH 04/11] More robustly reject relaxing non-default trait bounds --- .../src/hir_ty_lowering/bounds.rs | 49 ++++++++++++- .../src/hir_ty_lowering/errors.rs | 48 +------------ .../src/hir_ty_lowering/mod.rs | 4 +- compiler/rustc_middle/src/ty/context.rs | 4 +- .../feature-gate-more-maybe-bounds.rs | 15 ++-- .../feature-gate-more-maybe-bounds.stderr | 42 ++++++++--- .../ui/sized-hierarchy/default-supertrait.rs | 7 +- .../sized-hierarchy/default-supertrait.stderr | 72 +++++++++++++++---- .../relaxing-default-bound-error-37534.rs | 8 +-- .../relaxing-default-bound-error-37534.stderr | 10 +-- tests/ui/trait-bounds/more_maybe_bounds.rs | 22 +++--- .../ui/trait-bounds/more_maybe_bounds.stderr | 60 +++++++++++++--- 12 files changed, 224 insertions(+), 117 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index b6c4ca3b95949..a3cb49e32d808 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -204,7 +204,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // FIXME(more_maybe_bounds): We don't call this for trait object tys, supertrait // bounds, trait alias bounds, assoc type bounds (ATB)! let bounds = collect_relaxed_bounds(hir_bounds, self_ty_where_predicates); - self.check_and_report_invalid_relaxed_bounds(bounds); + self.reject_duplicate_relaxed_bounds(bounds); } let collected = collect_sizedness_bounds(tcx, hir_bounds, self_ty_where_predicates, span); @@ -308,6 +308,53 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { !self.tcx().has_attr(CRATE_DEF_ID, sym::rustc_no_implicit_bounds) && !collected.any() } + fn reject_duplicate_relaxed_bounds(&self, relaxed_bounds: SmallVec<[&PolyTraitRef<'_>; 1]>) { + let tcx = self.tcx(); + + let mut grouped_bounds = FxIndexMap::<_, Vec<_>>::default(); + + for bound in &relaxed_bounds { + if let Res::Def(DefKind::Trait, trait_def_id) = bound.trait_ref.path.res { + grouped_bounds.entry(trait_def_id).or_default().push(bound.span); + } + } + + for (trait_def_id, spans) in grouped_bounds { + if spans.len() > 1 { + let name = tcx.item_name(trait_def_id); + self.dcx() + .struct_span_err(spans, format!("duplicate relaxed `{name}` bounds")) + .with_code(E0203) + .emit(); + } + } + } + + pub(crate) fn require_bound_to_relax_default_trait( + &self, + trait_ref: hir::TraitRef<'_>, + span: Span, + ) { + let tcx = self.tcx(); + + if let Res::Def(DefKind::Trait, def_id) = trait_ref.path.res + && (tcx.is_lang_item(def_id, hir::LangItem::Sized) || tcx.is_default_trait(def_id)) + { + return; + } + + self.dcx().span_err( + span, + if tcx.sess.opts.unstable_opts.experimental_default_bounds + || tcx.features().more_maybe_bounds() + { + "bound modifier `?` can only be applied to default traits" + } else { + "bound modifier `?` can only be applied to `Sized`" + }, + ); + } + /// Lower HIR bounds into `bounds` given the self type `param_ty` and the overarching late-bound vars if any. /// /// ### Examples diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 165051744641f..1ce541ffb2797 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -8,7 +8,7 @@ use rustc_errors::{ }; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, HirId, PolyTraitRef}; +use rustc_hir::{self as hir, HirId}; use rustc_middle::bug; use rustc_middle::ty::fast_reject::{TreatParams, simplify_type}; use rustc_middle::ty::print::{PrintPolyTraitRefExt as _, PrintTraitRefExt as _}; @@ -35,52 +35,6 @@ use crate::fluent_generated as fluent; use crate::hir_ty_lowering::{AssocItemQSelf, HirTyLowerer}; impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { - /// Check for duplicate relaxed bounds and relaxed bounds of non-default traits. - pub(crate) fn check_and_report_invalid_relaxed_bounds( - &self, - relaxed_bounds: SmallVec<[&PolyTraitRef<'_>; 1]>, - ) { - let tcx = self.tcx(); - - let mut grouped_bounds = FxIndexMap::<_, Vec<_>>::default(); - - for bound in &relaxed_bounds { - if let Res::Def(DefKind::Trait, trait_def_id) = bound.trait_ref.path.res { - grouped_bounds.entry(trait_def_id).or_default().push(bound.span); - } - } - - for (trait_def_id, spans) in grouped_bounds { - if spans.len() > 1 { - let name = tcx.item_name(trait_def_id); - self.dcx() - .struct_span_err(spans, format!("duplicate relaxed `{name}` bounds")) - .with_code(E0203) - .emit(); - } - } - - let sized_def_id = tcx.require_lang_item(hir::LangItem::Sized, DUMMY_SP); - - for bound in relaxed_bounds { - if let Res::Def(DefKind::Trait, def_id) = bound.trait_ref.path.res - && (def_id == sized_def_id || tcx.is_default_trait(def_id)) - { - continue; - } - self.dcx().span_err( - bound.span, - if tcx.sess.opts.unstable_opts.experimental_default_bounds - || tcx.features().more_maybe_bounds() - { - "bound modifier `?` can only be applied to default traits like `Sized`" - } else { - "bound modifier `?` can only be applied to `Sized`" - }, - ); - } - } - /// On missing type parameters, emit an E0393 error and provide a structured suggestion using /// the type parameter's name as a placeholder. pub(crate) fn report_missing_type_params( 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 eb660804c2b52..fe182c8e70433 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -767,7 +767,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // We use the *resolved* bound vars later instead of the HIR ones since the former // also include the bound vars of the overarching predicate if applicable. - let hir::PolyTraitRef { bound_generic_params: _, modifiers, ref trait_ref, span } = + let hir::PolyTraitRef { bound_generic_params: _, modifiers, trait_ref, span } = *poly_trait_ref; let hir::TraitBoundModifiers { constness, polarity } = modifiers; @@ -791,6 +791,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { rustc_ast::BoundPolarity::Positive => (ty::PredicatePolarity::Positive, bounds), rustc_ast::BoundPolarity::Negative(_) => (ty::PredicatePolarity::Negative, bounds), rustc_ast::BoundPolarity::Maybe(_) => { + self.require_bound_to_relax_default_trait(trait_ref, span); + (ty::PredicatePolarity::Positive, &mut Vec::new()) } }; diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 3c5c21a7a89c2..2752d94c5bbb7 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1782,9 +1782,7 @@ impl<'tcx> TyCtxt<'tcx> { } pub fn is_default_trait(self, def_id: DefId) -> bool { - self.default_traits() - .iter() - .any(|&default_trait| self.lang_items().get(default_trait) == Some(def_id)) + self.default_traits().iter().any(|&default_trait| self.is_lang_item(def_id, default_trait)) } /// Returns a range of the start/end indices specified with the diff --git a/tests/ui/feature-gates/feature-gate-more-maybe-bounds.rs b/tests/ui/feature-gates/feature-gate-more-maybe-bounds.rs index 9c727ae3aad4c..191fef3eae457 100644 --- a/tests/ui/feature-gates/feature-gate-more-maybe-bounds.rs +++ b/tests/ui/feature-gates/feature-gate-more-maybe-bounds.rs @@ -1,12 +1,17 @@ -#![feature(auto_traits)] +#![feature(auto_traits, lang_items)] -trait Trait1 {} -auto trait Trait2 {} -trait Trait3: ?Trait1 {} //~ ERROR relaxed bounds are not permitted in supertrait bounds -trait Trait4 where Self: ?Trait1 {} //~ ERROR this relaxed bound is not permitted here +#[lang = "default_trait1"] trait Trait1 {} +#[lang = "default_trait2"] auto trait Trait2 {} + +trait Trait3: ?Trait1 {} +//~^ ERROR relaxed bounds are not permitted in supertrait bounds +//~| ERROR bound modifier `?` can only be applied to `Sized` +//~| ERROR bound modifier `?` can only be applied to `Sized` +//~| ERROR bound modifier `?` can only be applied to `Sized` fn foo(_: Box) {} //~^ ERROR relaxed bounds are not permitted in trait object types +//~| ERROR bound modifier `?` can only be applied to `Sized` fn bar(_: T) {} //~^ ERROR bound modifier `?` can only be applied to `Sized` //~| ERROR bound modifier `?` can only be applied to `Sized` diff --git a/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr b/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr index 092655b0f53a6..6503af2b5876d 100644 --- a/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr +++ b/tests/ui/feature-gates/feature-gate-more-maybe-bounds.stderr @@ -1,34 +1,54 @@ error: relaxed bounds are not permitted in supertrait bounds - --> $DIR/feature-gate-more-maybe-bounds.rs:5:15 + --> $DIR/feature-gate-more-maybe-bounds.rs:6:15 | LL | trait Trait3: ?Trait1 {} | ^^^^^^^ -error: this relaxed bound is not permitted here - --> $DIR/feature-gate-more-maybe-bounds.rs:6:26 +error: relaxed bounds are not permitted in trait object types + --> $DIR/feature-gate-more-maybe-bounds.rs:12:28 | -LL | trait Trait4 where Self: ?Trait1 {} - | ^^^^^^^ +LL | fn foo(_: Box) {} + | ^^^^^^^ + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/feature-gate-more-maybe-bounds.rs:6:15 | - = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item +LL | trait Trait3: ?Trait1 {} + | ^^^^^^^ -error: relaxed bounds are not permitted in trait object types - --> $DIR/feature-gate-more-maybe-bounds.rs:8:28 +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/feature-gate-more-maybe-bounds.rs:6:15 + | +LL | trait Trait3: ?Trait1 {} + | ^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/feature-gate-more-maybe-bounds.rs:6:15 + | +LL | trait Trait3: ?Trait1 {} + | ^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/feature-gate-more-maybe-bounds.rs:12:28 | LL | fn foo(_: Box) {} | ^^^^^^^ error: bound modifier `?` can only be applied to `Sized` - --> $DIR/feature-gate-more-maybe-bounds.rs:10:11 + --> $DIR/feature-gate-more-maybe-bounds.rs:15:11 | LL | fn bar(_: T) {} | ^^^^^^^ error: bound modifier `?` can only be applied to `Sized` - --> $DIR/feature-gate-more-maybe-bounds.rs:10:21 + --> $DIR/feature-gate-more-maybe-bounds.rs:15:21 | LL | fn bar(_: T) {} | ^^^^^^^ -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/sized-hierarchy/default-supertrait.rs b/tests/ui/sized-hierarchy/default-supertrait.rs index ab3b28e84db5a..d5bf152b7963b 100644 --- a/tests/ui/sized-hierarchy/default-supertrait.rs +++ b/tests/ui/sized-hierarchy/default-supertrait.rs @@ -12,12 +12,17 @@ trait MetaSized_: MetaSized { } trait NegMetaSized: ?MetaSized { } //~^ ERROR relaxed bounds are not permitted in supertrait bounds - +//~| ERROR bound modifier `?` can only be applied to `Sized` +//~| ERROR bound modifier `?` can only be applied to `Sized` +//~| ERROR bound modifier `?` can only be applied to `Sized` trait PointeeSized_: PointeeSized { } trait NegPointeeSized: ?PointeeSized { } //~^ ERROR relaxed bounds are not permitted in supertrait bounds +//~| ERROR bound modifier `?` can only be applied to `Sized` +//~| ERROR bound modifier `?` can only be applied to `Sized` +//~| ERROR bound modifier `?` can only be applied to `Sized` trait Bare {} diff --git a/tests/ui/sized-hierarchy/default-supertrait.stderr b/tests/ui/sized-hierarchy/default-supertrait.stderr index a4ade7fb83941..2a521dce8b6bb 100644 --- a/tests/ui/sized-hierarchy/default-supertrait.stderr +++ b/tests/ui/sized-hierarchy/default-supertrait.stderr @@ -13,19 +13,63 @@ LL | trait NegMetaSized: ?MetaSized { } | ^^^^^^^^^^ error: relaxed bounds are not permitted in supertrait bounds - --> $DIR/default-supertrait.rs:19:24 + --> $DIR/default-supertrait.rs:21:24 | LL | trait NegPointeeSized: ?PointeeSized { } | ^^^^^^^^^^^^^ +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/default-supertrait.rs:13:21 + | +LL | trait NegMetaSized: ?MetaSized { } + | ^^^^^^^^^^ + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/default-supertrait.rs:13:21 + | +LL | trait NegMetaSized: ?MetaSized { } + | ^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/default-supertrait.rs:13:21 + | +LL | trait NegMetaSized: ?MetaSized { } + | ^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/default-supertrait.rs:21:24 + | +LL | trait NegPointeeSized: ?PointeeSized { } + | ^^^^^^^^^^^^^ + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/default-supertrait.rs:21:24 + | +LL | trait NegPointeeSized: ?PointeeSized { } + | ^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/default-supertrait.rs:21:24 + | +LL | trait NegPointeeSized: ?PointeeSized { } + | ^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + error[E0277]: the size for values of type `T` cannot be known - --> $DIR/default-supertrait.rs:52:38 + --> $DIR/default-supertrait.rs:57:38 | LL | fn with_bare_trait() { | ^^^^ doesn't have a known size | note: required by a bound in `Bare` - --> $DIR/default-supertrait.rs:22:1 + --> $DIR/default-supertrait.rs:27:1 | LL | trait Bare {} | ^^^^^^^^^^^^^ required by this bound in `Bare` @@ -35,7 +79,7 @@ LL | fn with_bare_trait() { | ++++++++++++++++++++++++ error[E0277]: the size for values of type `T` cannot be known at compilation time - --> $DIR/default-supertrait.rs:35:22 + --> $DIR/default-supertrait.rs:40:22 | LL | fn with_metasized_supertrait() { | - this type parameter needs to be `Sized` @@ -43,13 +87,13 @@ LL | requires_sized::(); | ^ doesn't have a size known at compile-time | note: required by a bound in `requires_sized` - --> $DIR/default-supertrait.rs:24:22 + --> $DIR/default-supertrait.rs:29:22 | LL | fn requires_sized() {} | ^^^^^ required by this bound in `requires_sized` error[E0277]: the size for values of type `T` cannot be known at compilation time - --> $DIR/default-supertrait.rs:43:22 + --> $DIR/default-supertrait.rs:48:22 | LL | fn with_pointeesized_supertrait() { | - this type parameter needs to be `Sized` @@ -57,19 +101,19 @@ LL | requires_sized::(); | ^ doesn't have a size known at compile-time | note: required by a bound in `requires_sized` - --> $DIR/default-supertrait.rs:24:22 + --> $DIR/default-supertrait.rs:29:22 | LL | fn requires_sized() {} | ^^^^^ required by this bound in `requires_sized` error[E0277]: the size for values of type `T` cannot be known - --> $DIR/default-supertrait.rs:45:26 + --> $DIR/default-supertrait.rs:50:26 | LL | requires_metasized::(); | ^ doesn't have a known size | note: required by a bound in `requires_metasized` - --> $DIR/default-supertrait.rs:25:26 + --> $DIR/default-supertrait.rs:30:26 | LL | fn requires_metasized() {} | ^^^^^^^^^ required by this bound in `requires_metasized` @@ -79,7 +123,7 @@ LL | fn with_pointeesized_supertrait $DIR/default-supertrait.rs:54:22 + --> $DIR/default-supertrait.rs:59:22 | LL | fn with_bare_trait() { | - this type parameter needs to be `Sized` @@ -88,19 +132,19 @@ LL | requires_sized::(); | ^ doesn't have a size known at compile-time | note: required by a bound in `requires_sized` - --> $DIR/default-supertrait.rs:24:22 + --> $DIR/default-supertrait.rs:29:22 | LL | fn requires_sized() {} | ^^^^^ required by this bound in `requires_sized` error[E0277]: the size for values of type `T` cannot be known - --> $DIR/default-supertrait.rs:56:26 + --> $DIR/default-supertrait.rs:61:26 | LL | requires_metasized::(); | ^ doesn't have a known size | note: required by a bound in `requires_metasized` - --> $DIR/default-supertrait.rs:25:26 + --> $DIR/default-supertrait.rs:30:26 | LL | fn requires_metasized() {} | ^^^^^^^^^ required by this bound in `requires_metasized` @@ -109,6 +153,6 @@ help: consider further restricting type parameter `T` with unstable trait `MetaS LL | fn with_bare_trait() { | ++++++++++++++++++++++++ -error: aborting due to 9 previous errors +error: aborting due to 15 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/sized/relaxing-default-bound-error-37534.rs b/tests/ui/sized/relaxing-default-bound-error-37534.rs index d30e9f92ce9fb..5aca030908bfa 100644 --- a/tests/ui/sized/relaxing-default-bound-error-37534.rs +++ b/tests/ui/sized/relaxing-default-bound-error-37534.rs @@ -1,7 +1,5 @@ -struct Foo {} -//~^ ERROR expected trait, found derive macro `Hash` -//~| ERROR bound modifier `?` can only be applied to `Sized` +// issue: -fn main() {} +struct Foo {} //~ ERROR expected trait, found derive macro `Hash` -// https://github.com/rust-lang/rust/issues/37534 +fn main() {} diff --git a/tests/ui/sized/relaxing-default-bound-error-37534.stderr b/tests/ui/sized/relaxing-default-bound-error-37534.stderr index 8b9597f33e307..5de82206d2692 100644 --- a/tests/ui/sized/relaxing-default-bound-error-37534.stderr +++ b/tests/ui/sized/relaxing-default-bound-error-37534.stderr @@ -1,5 +1,5 @@ error[E0404]: expected trait, found derive macro `Hash` - --> $DIR/relaxing-default-bound-error-37534.rs:1:16 + --> $DIR/relaxing-default-bound-error-37534.rs:3:16 | LL | struct Foo {} | ^^^^ not a trait @@ -9,12 +9,6 @@ help: consider importing this trait instead LL + use std::hash::Hash; | -error: bound modifier `?` can only be applied to `Sized` - --> $DIR/relaxing-default-bound-error-37534.rs:1:15 - | -LL | struct Foo {} - | ^^^^^ - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0404`. diff --git a/tests/ui/trait-bounds/more_maybe_bounds.rs b/tests/ui/trait-bounds/more_maybe_bounds.rs index ddd4313bd5e15..e719327c8858f 100644 --- a/tests/ui/trait-bounds/more_maybe_bounds.rs +++ b/tests/ui/trait-bounds/more_maybe_bounds.rs @@ -1,40 +1,40 @@ -// FIXME(more_maybe_bounds): Even under `more_maybe_bounds` / `-Zexperimental-default-bounds`, -// trying to relax non-default bounds should still be an error in all contexts! As you can see -// there are places like supertrait bounds, trait object types or associated type bounds (ATB) -// where we currently don't perform this check. #![feature(auto_traits, more_maybe_bounds, negative_impls)] trait Trait1 {} auto trait Trait2 {} -// FIXME: `?Trait1` should be rejected, `Trait1` isn't marked `#[lang = "default_traitN"]`. trait Trait3: ?Trait1 {} +//~^ ERROR bound modifier `?` can only be applied to default traits +//~| ERROR bound modifier `?` can only be applied to default traits +//~| ERROR bound modifier `?` can only be applied to default traits trait Trait4 where Self: Trait1 {} -// FIXME: `?Trait2` should be rejected, `Trait2` isn't marked `#[lang = "default_traitN"]`. + fn foo(_: Box<(dyn Trait3 + ?Trait2)>) {} +//~^ ERROR bound modifier `?` can only be applied to default traits fn bar(_: &T) {} -//~^ ERROR bound modifier `?` can only be applied to default traits like `Sized` -//~| ERROR bound modifier `?` can only be applied to default traits like `Sized` -//~| ERROR bound modifier `?` can only be applied to default traits like `Sized` +//~^ ERROR bound modifier `?` can only be applied to default traits +//~| ERROR bound modifier `?` can only be applied to default traits +//~| ERROR bound modifier `?` can only be applied to default traits -// FIXME: `?Trait1` should be rejected, `Trait1` isn't marked `#[lang = "default_traitN"]`. fn baz() where T: Iterator {} //~^ ERROR this relaxed bound is not permitted here +//~| ERROR bound modifier `?` can only be applied to default traits struct S1(T); impl S1 { fn f() where T: ?Trait1 {} //~^ ERROR this relaxed bound is not permitted here + //~| ERROR bound modifier `?` can only be applied to default traits } trait Trait5<'a> {} struct S2(T) where for<'a> T: ?Trait5<'a>; //~^ ERROR this relaxed bound is not permitted here -//~| ERROR bound modifier `?` can only be applied to default traits like `Sized` +//~| ERROR bound modifier `?` can only be applied to default traits struct S; impl !Trait2 for S {} diff --git a/tests/ui/trait-bounds/more_maybe_bounds.stderr b/tests/ui/trait-bounds/more_maybe_bounds.stderr index b8782c560ad83..ef079213e1179 100644 --- a/tests/ui/trait-bounds/more_maybe_bounds.stderr +++ b/tests/ui/trait-bounds/more_maybe_bounds.stderr @@ -1,5 +1,5 @@ error: this relaxed bound is not permitted here - --> $DIR/more_maybe_bounds.rs:23:37 + --> $DIR/more_maybe_bounds.rs:21:37 | LL | fn baz() where T: Iterator {} | ^^^^^^^ @@ -7,7 +7,7 @@ LL | fn baz() where T: Iterator {} = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error: this relaxed bound is not permitted here - --> $DIR/more_maybe_bounds.rs:29:21 + --> $DIR/more_maybe_bounds.rs:28:21 | LL | fn f() where T: ?Trait1 {} | ^^^^^^^ @@ -22,29 +22,69 @@ LL | struct S2(T) where for<'a> T: ?Trait5<'a>; | = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item -error: bound modifier `?` can only be applied to default traits like `Sized` - --> $DIR/more_maybe_bounds.rs:17:20 +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:6:15 + | +LL | trait Trait3: ?Trait1 {} + | ^^^^^^^ + +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:6:15 + | +LL | trait Trait3: ?Trait1 {} + | ^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:6:15 + | +LL | trait Trait3: ?Trait1 {} + | ^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:13:29 + | +LL | fn foo(_: Box<(dyn Trait3 + ?Trait2)>) {} + | ^^^^^^^ + +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:16:20 | LL | fn bar(_: &T) {} | ^^^^^^^ -error: bound modifier `?` can only be applied to default traits like `Sized` - --> $DIR/more_maybe_bounds.rs:17:30 +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:16:30 | LL | fn bar(_: &T) {} | ^^^^^^^ -error: bound modifier `?` can only be applied to default traits like `Sized` - --> $DIR/more_maybe_bounds.rs:17:40 +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:16:40 | LL | fn bar(_: &T) {} | ^^^^^^^ -error: bound modifier `?` can only be applied to default traits like `Sized` +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:21:37 + | +LL | fn baz() where T: Iterator {} + | ^^^^^^^ + +error: bound modifier `?` can only be applied to default traits --> $DIR/more_maybe_bounds.rs:35:34 | LL | struct S2(T) where for<'a> T: ?Trait5<'a>; | ^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: bound modifier `?` can only be applied to default traits + --> $DIR/more_maybe_bounds.rs:28:21 + | +LL | fn f() where T: ?Trait1 {} + | ^^^^^^^ + +error: aborting due to 13 previous errors From 65814c80e8fb791b9837c5541a0c74d6ac066ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 16 Oct 2025 16:09:49 +0200 Subject: [PATCH 05/11] Guard us against degenerate default traits --- .../src/hir_ty_lowering/mod.rs | 109 ++++++++++-------- 1 file changed, 62 insertions(+), 47 deletions(-) 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 fe182c8e70433..f0ab6cd6dbab4 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -717,16 +717,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { trait_ref: &hir::TraitRef<'tcx>, self_ty: Ty<'tcx>, ) -> ty::TraitRef<'tcx> { - let _ = self.prohibit_generic_args( - trait_ref.path.segments.split_last().unwrap().1.iter(), - GenericsArgsErrExtend::None, - ); + let [leading_segments @ .., segment] = trait_ref.path.segments else { bug!() }; + + let _ = self.prohibit_generic_args(leading_segments.iter(), GenericsArgsErrExtend::None); self.lower_mono_trait_ref( trait_ref.path.span, trait_ref.trait_def_id().unwrap_or_else(|| FatalError.raise()), self_ty, - trait_ref.path.segments.last().unwrap(), + segment, true, ) } @@ -757,7 +756,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { #[instrument(level = "debug", skip(self, bounds))] pub(crate) fn lower_poly_trait_ref( &self, - poly_trait_ref: &hir::PolyTraitRef<'tcx>, + &hir::PolyTraitRef { + bound_generic_params, + modifiers: hir::TraitBoundModifiers { constness, polarity }, + trait_ref, + span, + }: &hir::PolyTraitRef<'tcx>, self_ty: Ty<'tcx>, bounds: &mut Vec<(ty::Clause<'tcx>, Span)>, predicate_filter: PredicateFilter, @@ -767,52 +771,67 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // We use the *resolved* bound vars later instead of the HIR ones since the former // also include the bound vars of the overarching predicate if applicable. - let hir::PolyTraitRef { bound_generic_params: _, modifiers, trait_ref, span } = - *poly_trait_ref; - let hir::TraitBoundModifiers { constness, polarity } = modifiers; + let _ = bound_generic_params; let trait_def_id = trait_ref.trait_def_id().unwrap_or_else(|| FatalError.raise()); - // Relaxed bounds `?Trait` and `PointeeSized` bounds aren't represented in the `middle::ty` IR + // Relaxed bounds `?Trait` and `PointeeSized` bounds aren't represented in the middle::ty IR // as they denote the *absence* of a default bound. However, we can't bail out early here since // we still need to perform several validation steps (see below). Instead, simply "pour" all // resulting bounds "down the drain", i.e., into a new `Vec` that just gets dropped at the end. - let (polarity, bounds) = match polarity { - rustc_ast::BoundPolarity::Positive - if tcx.is_lang_item(trait_def_id, hir::LangItem::PointeeSized) => - { + let transient = match polarity { + hir::BoundPolarity::Positive => { // To elaborate on the comment directly above, regarding `PointeeSized` specifically, // we don't "reify" such bounds to avoid trait system limitations -- namely, // non-global where-clauses being preferred over item bounds (where `PointeeSized` // bounds would be proven) -- which can result in errors when a `PointeeSized` // supertrait / bound / predicate is added to some items. - (ty::PredicatePolarity::Positive, &mut Vec::new()) + tcx.is_lang_item(trait_def_id, hir::LangItem::PointeeSized) } - rustc_ast::BoundPolarity::Positive => (ty::PredicatePolarity::Positive, bounds), - rustc_ast::BoundPolarity::Negative(_) => (ty::PredicatePolarity::Negative, bounds), - rustc_ast::BoundPolarity::Maybe(_) => { + hir::BoundPolarity::Negative(_) => false, + hir::BoundPolarity::Maybe(_) => { self.require_bound_to_relax_default_trait(trait_ref, span); + true + } + }; + let bounds = if transient { &mut Vec::new() } else { bounds }; - (ty::PredicatePolarity::Positive, &mut Vec::new()) + let polarity = match polarity { + hir::BoundPolarity::Positive | hir::BoundPolarity::Maybe(_) => { + ty::PredicatePolarity::Positive } + hir::BoundPolarity::Negative(_) => ty::PredicatePolarity::Negative, }; - let trait_segment = trait_ref.path.segments.last().unwrap(); + let [leading_segments @ .., segment] = trait_ref.path.segments else { bug!() }; - let _ = self.prohibit_generic_args( - trait_ref.path.segments.split_last().unwrap().1.iter(), - GenericsArgsErrExtend::None, - ); - self.report_internal_fn_trait(span, trait_def_id, trait_segment, false); + let _ = self.prohibit_generic_args(leading_segments.iter(), GenericsArgsErrExtend::None); + self.report_internal_fn_trait(span, trait_def_id, segment, false); let (generic_args, arg_count) = self.lower_generic_args_of_path( trait_ref.path.span, trait_def_id, &[], - trait_segment, + segment, Some(self_ty), ); + let constraints = segment.args().constraints; + + if transient && (!generic_args[1..].is_empty() || !constraints.is_empty()) { + // Since the bound won't be present in the middle::ty IR as established above, any + // arguments or constraints won't be checked for well-formedness in later passes. + // + // This is only an issue if the trait ref is otherwise valid which can only happen if + // the corresponding default trait has generic parameters or associated items. Such a + // trait would be degenerate. We delay a bug to detect and guard us against these. + // + // E.g: Given `/*default*/ trait Bound<'a: 'static, T, const N: usize> {}`, + // `?Bound, { panic!() }>` won't be wfchecked. + self.dcx() + .span_delayed_bug(span, "transient bound should not have args or constraints"); + } + let bound_vars = tcx.late_bound_vars(trait_ref.hir_ref_id); debug!(?bound_vars); @@ -924,7 +943,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { == OverlappingAsssocItemConstraints::Forbidden) .then_some(FxIndexMap::default()); - for constraint in trait_segment.args().constraints { + for constraint in constraints { // Don't register any associated item constraints for negative bounds, // since we should have emitted an error for them earlier, and they // would not be well-formed! @@ -1916,10 +1935,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { Res::Def(DefKind::OpaqueTy, did) => { // Check for desugared `impl Trait`. assert_matches!(tcx.opaque_ty_origin(did), hir::OpaqueTyOrigin::TyAlias { .. }); - let item_segment = path.segments.split_last().unwrap(); - let _ = self - .prohibit_generic_args(item_segment.1.iter(), GenericsArgsErrExtend::OpaqueTy); - let args = self.lower_generic_args_of_path_segment(span, did, item_segment.0); + let [leading_segments @ .., segment] = path.segments else { bug!() }; + let _ = self.prohibit_generic_args( + leading_segments.iter(), + GenericsArgsErrExtend::OpaqueTy, + ); + let args = self.lower_generic_args_of_path_segment(span, did, segment); Ty::new_opaque(tcx, did, args) } Res::Def( @@ -1931,11 +1952,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { did, ) => { assert_eq!(opt_self_ty, None); - let _ = self.prohibit_generic_args( - path.segments.split_last().unwrap().1.iter(), - GenericsArgsErrExtend::None, - ); - self.lower_path_segment(span, did, path.segments.last().unwrap()) + let [leading_segments @ .., segment] = path.segments else { bug!() }; + let _ = self + .prohibit_generic_args(leading_segments.iter(), GenericsArgsErrExtend::None); + self.lower_path_segment(span, did, segment) } Res::Def(kind @ DefKind::Variant, def_id) if let PermitVariants::Yes = permit_variants => @@ -1955,8 +1975,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { GenericsArgsErrExtend::DefVariant(&path.segments), ); - let GenericPathSegment(def_id, index) = generic_segments.last().unwrap(); - self.lower_path_segment(span, *def_id, &path.segments[*index]) + let &GenericPathSegment(def_id, index) = generic_segments.last().unwrap(); + self.lower_path_segment(span, def_id, &path.segments[index]) } Res::Def(DefKind::TyParam, def_id) => { assert_eq!(opt_self_ty, None); @@ -2242,15 +2262,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } Res::Def(DefKind::Const | DefKind::Ctor(_, CtorKind::Const), did) => { assert_eq!(opt_self_ty, None); - let _ = self.prohibit_generic_args( - path.segments.split_last().unwrap().1.iter(), - GenericsArgsErrExtend::None, - ); - let args = self.lower_generic_args_of_path_segment( - span, - did, - path.segments.last().unwrap(), - ); + let [leading_segments @ .., segment] = path.segments else { bug!() }; + let _ = self + .prohibit_generic_args(leading_segments.iter(), GenericsArgsErrExtend::None); + let args = self.lower_generic_args_of_path_segment(span, did, segment); ty::Const::new_unevaluated(tcx, ty::UnevaluatedConst::new(did, args)) } Res::Def(DefKind::AssocConst, did) => { From 0f9336312484fd8e57ad399a9ce62ea328d4e8bf Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Sun, 19 Oct 2025 19:18:18 +0200 Subject: [PATCH 06/11] enzyme/autodiff is compatible with download-ci=true --- src/bootstrap/src/core/config/toml/llvm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/config/toml/llvm.rs b/src/bootstrap/src/core/config/toml/llvm.rs index 9751837a88794..9523f80214849 100644 --- a/src/bootstrap/src/core/config/toml/llvm.rs +++ b/src/bootstrap/src/core/config/toml/llvm.rs @@ -117,7 +117,7 @@ pub fn check_incompatible_options_for_ci_llvm( enable_warnings, download_ci_llvm: _, build_config, - enzyme, + enzyme: _, } = ci_llvm_config; err!(current_llvm_config.optimize, optimize); @@ -139,7 +139,6 @@ pub fn check_incompatible_options_for_ci_llvm( err!(current_llvm_config.clang, clang); err!(current_llvm_config.build_config, build_config); err!(current_llvm_config.plugins, plugins); - err!(current_llvm_config.enzyme, enzyme); warn!(current_llvm_config.enable_warnings, enable_warnings); From 788717b60f75116afeb777f7ee397e2f0df43686 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Sun, 19 Oct 2025 20:28:00 +0200 Subject: [PATCH 07/11] update autodiff docs in the dev guide --- src/doc/rustc-dev-guide/src/autodiff/debugging.md | 2 +- src/doc/rustc-dev-guide/src/autodiff/installation.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/autodiff/debugging.md b/src/doc/rustc-dev-guide/src/autodiff/debugging.md index 97893535cfe54..7c7af8589868f 100644 --- a/src/doc/rustc-dev-guide/src/autodiff/debugging.md +++ b/src/doc/rustc-dev-guide/src/autodiff/debugging.md @@ -25,7 +25,7 @@ The actual numbers will depend on your code. ## 2) Check your llvm-ir reproducer -To confirm that your previous step worked, we will use llvm's `opt` tool. find your path to the opt binary, with a path similar to `/rust/build//build/bin/opt`. also find `llvmenzyme-19.` path, similar to `/rust/build/target-triple/enzyme/build/enzyme/llvmenzyme-19`. Please keep in mind that llvm frequently updates it's llvm backend, so the version number might be higher (20, 21, ...). Once you have both, run the following command: +To confirm that your previous step worked, we will use llvm's `opt` tool. Find your path to the opt binary, with a path similar to `/rust/build//ci-llvm/bin/opt`. If you build LLVM from source, you'll likely need to replace `ci-llvm` with `build`. Also find `llvmenzyme-21.` path, similar to `/rust/build/target-triple/enzyme/build/enzyme/llvmenzyme-21`. Please keep in mind that llvm frequently updates it's llvm backend, so the version number might be higher (20, 21, ...). Once you have both, run the following command: ```sh out.ll -load-pass-plugin=/path/to/build//stage1/lib/libEnzyme-21.so -passes="enzyme" -enzyme-strict-aliasing=0 -s diff --git a/src/doc/rustc-dev-guide/src/autodiff/installation.md b/src/doc/rustc-dev-guide/src/autodiff/installation.md index ddbb3a054241a..c9b6c85ab7a89 100644 --- a/src/doc/rustc-dev-guide/src/autodiff/installation.md +++ b/src/doc/rustc-dev-guide/src/autodiff/installation.md @@ -8,7 +8,7 @@ First you need to clone and configure the Rust repository: ```bash git clone git@github.com:rust-lang/rust cd rust -./configure --enable-llvm-link-shared --enable-llvm-plugins --enable-llvm-enzyme --release-channel=nightly --enable-llvm-assertions --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs +./configure --release-channel=nightly --enable-llvm-enzyme --enable-llvm-assertions --enable-option-checking --disable-docs --set llvm.download-ci-llvm=true ``` Afterwards you can build rustc using: @@ -47,7 +47,7 @@ Then build rustc in a slightly altered way: ```bash git clone https://github.com/rust-lang/rust cd rust -./configure --enable-llvm-link-shared --enable-llvm-plugins --enable-llvm-enzyme --release-channel=nightly --enable-llvm-assertions --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs +./configure --release-channel=nightly --enable-llvm-enzyme --enable-llvm-assertions --enable-option-checking --disable-docs --set llvm.download-ci-llvm=true ./x dist ``` We then copy the tarball to our host. The dockerid is the newest entry under `docker ps -a`. @@ -84,5 +84,5 @@ cd build cmake .. -G Ninja -DLLVM_DIR=/llvm-project/build/lib/cmake/llvm/ -DLLVM_EXTERNAL_LIT=/llvm-project/llvm/utils/lit/lit.py -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=YES -DBUILD_SHARED_LIBS=ON ninja ``` -This will build Enzyme, and you can find it in `Enzyme/enzyme/build/lib/Enzyme.so`. (Endings might differ based on your OS). +This will build Enzyme, and you can find it in `Enzyme/enzyme/build/lib/Enzyme.so`. (Endings might differ based on your OS). From 3e32dc27e37859ea6526e2da46e48e56aaf25bb1 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Sun, 19 Oct 2025 20:41:45 +0200 Subject: [PATCH 08/11] update CONFIG_CHANGE_HISTORY --- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index ea5fc77c8a4a8..921f57eb66d6e 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -571,4 +571,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Warning, summary: "`rust.lld = true` no longer automatically causes the `x86_64-unknown-linux-gnu` target to default into using the self-contained LLD linker. This target now uses the LLD linker by default. To opt out, set `target.x86_64-unknown-linux-gnu.default-linker-linux-override = 'off'`.", }, + ChangeInfo { + change_id: 147888, + severity: ChangeSeverity::Info, + summary: "`llvm.enzyme` now works with `download-ci-llvm=true`.", + }, ]; From 84861fa7653cfd11c850bf06cc01daa9d5f45124 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 20 Oct 2025 14:12:23 +1100 Subject: [PATCH 09/11] Skip up-to-date checking for tests that are already ignored --- src/tools/compiletest/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 4cfb1e20f9ae9..2c68a771169e2 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -907,7 +907,10 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te // If a test's inputs haven't changed since the last time it ran, // mark it as ignored so that the executor will skip it. - if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) { + if !desc.ignore + && !cx.config.force_rerun + && is_up_to_date(cx, testpaths, &early_props, revision) + { desc.ignore = true; // Keep this in sync with the "up-to-date" message detected by bootstrap. // FIXME(Zalathar): Now that we are no longer tied to libtest, we could From d828c11f96df3bce24433238a867f64f1fe6b28b Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 19 Oct 2025 18:33:12 +1100 Subject: [PATCH 10/11] Move `AuxProps` out of `EarlyProps` The primary purpose of `EarlyProps` is to discover revisions, so that we can create a separate test structure for each revision. Revisions can (and do) have different auxiliaries, and up-to-date checking is already done per-revision, so it makes more sense to perform up-to-date checks based on the current revisions's auxiliaries only. --- src/tools/compiletest/src/directives.rs | 14 +++---- src/tools/compiletest/src/directives/tests.rs | 41 ++++++------------- src/tools/compiletest/src/lib.rs | 21 +++++++--- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index e8b6b377bf40d..fe352b4fa3f21 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -8,7 +8,8 @@ use tracing::*; use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode}; use crate::debuggers::{extract_cdb_version, extract_gdb_version}; -use crate::directives::auxiliary::{AuxProps, parse_and_update_aux}; +pub(crate) use crate::directives::auxiliary::AuxProps; +use crate::directives::auxiliary::parse_and_update_aux; use crate::directives::directive_names::{ KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES, }; @@ -21,7 +22,7 @@ use crate::executor::{CollectedTestDesc, ShouldPanic}; use crate::util::static_regex; use crate::{fatal, help}; -pub(crate) mod auxiliary; +mod auxiliary; mod cfg; mod directive_names; mod file; @@ -44,10 +45,6 @@ impl DirectivesCache { /// the test. #[derive(Default)] pub(crate) struct EarlyProps { - /// Auxiliary crates that should be built and made available to this test. - /// Included in [`EarlyProps`] so that the indicated files can participate - /// in up-to-date checking. Building happens via [`TestProps::aux`] instead. - pub(crate) aux: AuxProps, pub(crate) revisions: Vec, } @@ -66,7 +63,6 @@ impl EarlyProps { file_directives, // (dummy comment to force args into vertical layout) &mut |ln: &DirectiveLine<'_>| { - parse_and_update_aux(config, ln, &mut props.aux); config.parse_and_update_revisions(ln, &mut props.revisions); }, ); @@ -1310,6 +1306,7 @@ pub(crate) fn make_test_description( file_directives: &FileDirectives<'_>, test_revision: Option<&str>, poisoned: &mut bool, + aux_props: &mut AuxProps, ) -> CollectedTestDesc { let mut ignore = false; let mut ignore_message = None; @@ -1327,6 +1324,9 @@ pub(crate) fn make_test_description( return; } + // Parse `aux-*` directives, for use by up-to-date checks. + parse_and_update_aux(config, ln, aux_props); + macro_rules! decision { ($e:expr) => { match $e { diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index b683c8317e49b..b204b57403ace 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -3,8 +3,9 @@ use semver::Version; use crate::common::{Config, Debugger, TestMode}; use crate::directives::{ - DirectivesCache, EarlyProps, Edition, EditionRange, FileDirectives, extract_llvm_version, - extract_version_range, iter_directives, line_directive, parse_edition, parse_normalize_rule, + AuxProps, DirectivesCache, EarlyProps, Edition, EditionRange, FileDirectives, + extract_llvm_version, extract_version_range, iter_directives, line_directive, parse_edition, + parse_normalize_rule, }; use crate::executor::{CollectedTestDesc, ShouldPanic}; @@ -20,6 +21,7 @@ fn make_test_description( let mut poisoned = false; let file_directives = FileDirectives::from_file_contents(path, file_contents); + let mut aux_props = AuxProps::default(); let test = crate::directives::make_test_description( config, &cache, @@ -29,6 +31,7 @@ fn make_test_description( &file_directives, revision, &mut poisoned, + &mut aux_props, ); if poisoned { panic!("poisoned!"); @@ -225,7 +228,7 @@ fn cfg() -> ConfigBuilder { ConfigBuilder::default() } -fn parse_rs(config: &Config, contents: &str) -> EarlyProps { +fn parse_early_props(config: &Config, contents: &str) -> EarlyProps { let file_directives = FileDirectives::from_file_contents(Utf8Path::new("a.rs"), contents); EarlyProps::from_file_directives(config, &file_directives) } @@ -253,25 +256,7 @@ fn should_fail() { fn revisions() { let config: Config = cfg().build(); - assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],); -} - -#[test] -fn aux_build() { - let config: Config = cfg().build(); - - assert_eq!( - parse_rs( - &config, - r" - //@ aux-build: a.rs - //@ aux-build: b.rs - " - ) - .aux - .builds, - vec!["a.rs", "b.rs"], - ); + assert_eq!(parse_early_props(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],); } #[test] @@ -550,7 +535,7 @@ fn test_extract_version_range() { #[should_panic(expected = "duplicate revision: `rpass1` in line ` rpass1 rpass1`")] fn test_duplicate_revisions() { let config: Config = cfg().build(); - parse_rs(&config, "//@ revisions: rpass1 rpass1"); + parse_early_props(&config, "//@ revisions: rpass1 rpass1"); } #[test] @@ -559,14 +544,14 @@ fn test_duplicate_revisions() { )] fn test_assembly_mode_forbidden_revisions() { let config = cfg().mode("assembly").build(); - parse_rs(&config, "//@ revisions: CHECK"); + parse_early_props(&config, "//@ revisions: CHECK"); } #[test] #[should_panic(expected = "revision name `true` is not permitted")] fn test_forbidden_revisions() { let config = cfg().mode("ui").build(); - parse_rs(&config, "//@ revisions: true"); + parse_early_props(&config, "//@ revisions: true"); } #[test] @@ -575,7 +560,7 @@ fn test_forbidden_revisions() { )] fn test_codegen_mode_forbidden_revisions() { let config = cfg().mode("codegen").build(); - parse_rs(&config, "//@ revisions: CHECK"); + parse_early_props(&config, "//@ revisions: CHECK"); } #[test] @@ -584,7 +569,7 @@ fn test_codegen_mode_forbidden_revisions() { )] fn test_miropt_mode_forbidden_revisions() { let config = cfg().mode("mir-opt").build(); - parse_rs(&config, "//@ revisions: CHECK"); + parse_early_props(&config, "//@ revisions: CHECK"); } #[test] @@ -608,7 +593,7 @@ fn test_forbidden_revisions_allowed_in_non_filecheck_dir() { let content = format!("//@ revisions: {rev}"); for mode in modes { let config = cfg().mode(mode).build(); - parse_rs(&config, &content); + parse_early_props(&config, &content); } } } diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 2c68a771169e2..798008ab432ed 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -41,7 +41,7 @@ use crate::common::{ CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS, expected_output_path, output_base_dir, output_relative_path, }; -use crate::directives::{DirectivesCache, FileDirectives}; +use crate::directives::{AuxProps, DirectivesCache, FileDirectives}; use crate::edition::parse_edition; use crate::executor::{CollectedTest, ColorConfig}; @@ -891,6 +891,11 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te // Create a test name and description to hand over to the executor. let (test_name, filterable_path) = make_test_name_and_filterable_path(&cx.config, testpaths, revision); + + // While scanning for ignore/only/needs directives, also collect aux + // paths for up-to-date checking. + let mut aux_props = AuxProps::default(); + // Create a description struct for the test/revision. // This is where `ignore-*`/`only-*`/`needs-*` directives are handled, // because they historically needed to set the libtest ignored flag. @@ -903,13 +908,14 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te &file_directives, revision, &mut collector.poisoned, + &mut aux_props, ); // If a test's inputs haven't changed since the last time it ran, // mark it as ignored so that the executor will skip it. if !desc.ignore && !cx.config.force_rerun - && is_up_to_date(cx, testpaths, &early_props, revision) + && is_up_to_date(cx, testpaths, &aux_props, revision) { desc.ignore = true; // Keep this in sync with the "up-to-date" message detected by bootstrap. @@ -939,7 +945,7 @@ fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str fn files_related_to_test( config: &Config, testpaths: &TestPaths, - props: &EarlyProps, + aux_props: &AuxProps, revision: Option<&str>, ) -> Vec { let mut related = vec![]; @@ -956,8 +962,11 @@ fn files_related_to_test( related.push(testpaths.file.clone()); } - for aux in props.aux.all_aux_path_strings() { + for aux in aux_props.all_aux_path_strings() { // FIXME(Zalathar): Perform all `auxiliary` path resolution in one place. + // FIXME(Zalathar): This only finds auxiliary files used _directly_ by + // the test file; if a transitive auxiliary is modified, the test might + // be treated as "up-to-date" even though it should run. let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux); related.push(path); } @@ -982,7 +991,7 @@ fn files_related_to_test( fn is_up_to_date( cx: &TestCollectorCx, testpaths: &TestPaths, - props: &EarlyProps, + aux_props: &AuxProps, revision: Option<&str>, ) -> bool { let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision); @@ -1003,7 +1012,7 @@ fn is_up_to_date( // Check the timestamp of the stamp file against the last modified time // of all files known to be relevant to the test. let mut inputs_stamp = cx.common_inputs_stamp.clone(); - for path in files_related_to_test(&cx.config, testpaths, props, revision) { + for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) { inputs_stamp.add_path(&path); } From e9bcded695f274fcd9c8fe8d5ffcf1d063a8ee85 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 20 Oct 2025 20:11:45 +1100 Subject: [PATCH 11/11] Store the selected edition in `TestProps` --- src/tools/compiletest/src/directives.rs | 20 +++++++++---------- src/tools/compiletest/src/directives/tests.rs | 12 +++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index e8b6b377bf40d..02dee5ee17874 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -81,11 +81,15 @@ impl EarlyProps { } #[derive(Clone, Debug)] -pub struct TestProps { +pub(crate) struct TestProps { // Lines that should be expected, in order, on standard out pub error_patterns: Vec, // Regexes that should be expected, in order, on standard out pub regex_error_patterns: Vec, + /// Edition selected by an `//@ edition` directive, if any. + /// + /// Automatically added to `compile_flags` during directive processing. + pub edition: Option, // Extra flags to pass to the compiler pub compile_flags: Vec, // Extra flags to pass when the compiled code is run (such as --bench) @@ -267,6 +271,7 @@ impl TestProps { TestProps { error_patterns: vec![], regex_error_patterns: vec![], + edition: None, compile_flags: vec![], run_flags: vec![], doc_flags: vec![], @@ -355,7 +360,6 @@ impl TestProps { /// `//@[foo]`), then the property is ignored unless `test_revision` is /// `Some("foo")`. fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) { - let mut has_edition = false; if !testfile.is_dir() { let file_contents = fs::read_to_string(testfile).unwrap(); let file_directives = FileDirectives::from_file_contents(testfile, &file_contents); @@ -423,13 +427,7 @@ impl TestProps { } if let Some(range) = parse_edition_range(config, ln) { - // The edition is added at the start, since flags from //@compile-flags must - // be passed to rustc last. - self.compile_flags.insert( - 0, - format!("--edition={}", range.edition_to_test(config.edition)), - ); - has_edition = true; + self.edition = Some(range.edition_to_test(config.edition)); } config.parse_and_update_revisions(ln, &mut self.revisions); @@ -678,10 +676,10 @@ impl TestProps { } } - if let (Some(edition), false) = (&config.edition, has_edition) { + if let Some(edition) = self.edition.or(config.edition) { // The edition is added at the start, since flags from //@compile-flags must be passed // to rustc last. - self.compile_flags.insert(0, format!("--edition={}", edition)); + self.compile_flags.insert(0, format!("--edition={edition}")); } } diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index b683c8317e49b..d50067a8f16b5 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -961,6 +961,18 @@ fn parse_edition_range(line: &str) -> Option { super::parse_edition_range(&config, &line) } +#[test] +fn edition_order() { + let editions = &[ + Edition::Year(2015), + Edition::Year(2018), + Edition::Year(2021), + Edition::Year(2024), + Edition::Future, + ]; + assert!(editions.is_sorted(), "{editions:#?}"); +} + #[test] fn test_parse_edition_range() { assert_eq!(None, parse_edition_range("hello-world"));