Rollup merge of #142693 - fmease:unbound-bettering, r=compiler-errors

More robustly deal with relaxed bounds and improve their diagnostics

Scaffolding for https://github.com/rust-lang/rust/issues/135229 (CC https://github.com/rust-lang/rust/pull/135331)

Fixes https://github.com/rust-lang/rust/issues/136944 (6th commit).
Fixes https://github.com/rust-lang/rust/issues/142718 (8th commit).
This commit is contained in:
Matthias Krüger
2025-07-18 19:14:43 +02:00
committed by GitHub
65 changed files with 772 additions and 819 deletions

View File

@@ -267,20 +267,16 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
match predicate.kind {
hir::WherePredicateKind::BoundPredicate(bound_pred) => {
let ty = icx.lowerer().lower_ty_maybe_return_type_notation(bound_pred.bounded_ty);
let bound_vars = tcx.late_bound_vars(predicate.hir_id);
// Keep the type around in a dummy predicate, in case of no bounds.
// That way, `where Ty:` is not a complete noop (see #53696) and `Ty`
// is still checked for WF.
// This is a `where Ty:` (sic!).
if bound_pred.bounds.is_empty() {
if let ty::Param(_) = ty.kind() {
// This is a `where T:`, which can be in the HIR from the
// transformation that moves `?Sized` to `T`'s declaration.
// We can skip the predicate because type parameters are
// trivially WF, but also we *should*, to avoid exposing
// users who never wrote `where Type:,` themselves, to
// compiler/tooling bugs from not handling WF predicates.
// We can skip the predicate because type parameters are trivially WF.
} else {
// Keep the type around in a dummy predicate. That way, it's not a complete
// noop (see #53696) and `Ty` is still checked for WF.
let span = bound_pred.bounded_ty.span;
let predicate = ty::Binder::bind_with_vars(
ty::ClauseKind::WellFormed(ty.into()),

View File

@@ -279,13 +279,6 @@ pub(crate) struct CopyImplOnTypeWithDtor {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(hir_analysis_multiple_relaxed_default_bounds, code = E0203)]
pub(crate) struct MultipleRelaxedDefaultBounds {
#[primary_span]
pub spans: Vec<Span>,
}
#[derive(Diagnostic)]
#[diag(hir_analysis_copy_impl_on_non_adt, code = E0206)]
pub(crate) struct CopyImplOnNonAdt {
@@ -319,13 +312,6 @@ pub(crate) struct TraitObjectDeclaredWithNoTraits {
pub trait_alias_span: Option<Span>,
}
#[derive(Diagnostic)]
#[diag(hir_analysis_pointee_sized_trait_object)]
pub(crate) struct PointeeSizedTraitObject {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(hir_analysis_ambiguous_lifetime_bound, code = E0227)]
pub(crate) struct AmbiguousLifetimeBound {

View File

@@ -6,7 +6,7 @@ use rustc_errors::struct_span_code_err;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId};
use rustc_hir::{AmbigArg, LangItem, PolyTraitRef};
use rustc_hir::{AmbigArg, PolyTraitRef};
use rustc_middle::bug;
use rustc_middle::ty::{
self as ty, IsSuggestable, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
@@ -85,17 +85,17 @@ fn search_bounds_for<'tcx>(
}
}
fn collect_unbounds<'tcx>(
fn collect_relaxed_bounds<'tcx>(
hir_bounds: &'tcx [hir::GenericBound<'tcx>],
self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>,
) -> SmallVec<[&'tcx PolyTraitRef<'tcx>; 1]> {
let mut unbounds: SmallVec<[_; 1]> = SmallVec::new();
let mut relaxed_bounds: SmallVec<[_; 1]> = SmallVec::new();
search_bounds_for(hir_bounds, self_ty_where_predicates, |ptr| {
if matches!(ptr.modifiers.polarity, hir::BoundPolarity::Maybe(_)) {
unbounds.push(ptr);
relaxed_bounds.push(ptr);
}
});
unbounds
relaxed_bounds
}
fn collect_bounds<'a, 'tcx>(
@@ -124,13 +124,13 @@ fn collect_sizedness_bounds<'tcx>(
self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>,
span: Span,
) -> CollectedSizednessBounds {
let sized_did = tcx.require_lang_item(LangItem::Sized, span);
let sized_did = tcx.require_lang_item(hir::LangItem::Sized, span);
let sized = collect_bounds(hir_bounds, self_ty_where_predicates, sized_did);
let meta_sized_did = tcx.require_lang_item(LangItem::MetaSized, span);
let meta_sized_did = tcx.require_lang_item(hir::LangItem::MetaSized, span);
let meta_sized = collect_bounds(hir_bounds, self_ty_where_predicates, meta_sized_did);
let pointee_sized_did = tcx.require_lang_item(LangItem::PointeeSized, span);
let pointee_sized_did = tcx.require_lang_item(hir::LangItem::PointeeSized, span);
let pointee_sized = collect_bounds(hir_bounds, self_ty_where_predicates, pointee_sized_did);
CollectedSizednessBounds { sized, meta_sized, pointee_sized }
@@ -151,24 +151,6 @@ fn add_trait_bound<'tcx>(
}
impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// Skip `PointeeSized` bounds.
///
/// `PointeeSized` is a "fake bound" insofar as anywhere a `PointeeSized` bound exists, there
/// is actually the absence of any bounds. This avoids limitations around 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.
pub(crate) fn should_skip_sizedness_bound<'hir>(
&self,
bound: &'hir hir::GenericBound<'tcx>,
) -> bool {
bound
.trait_ref()
.and_then(|tr| tr.trait_def_id())
.map(|did| self.tcx().is_lang_item(did, LangItem::PointeeSized))
.unwrap_or(false)
}
/// Adds sizedness bounds to a trait, trait alias, parameter, opaque type or associated type.
///
/// - On parameters, opaque type and associated types, add default `Sized` bound if no explicit
@@ -193,8 +175,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
return;
}
let meta_sized_did = tcx.require_lang_item(LangItem::MetaSized, span);
let pointee_sized_did = tcx.require_lang_item(LangItem::PointeeSized, span);
let meta_sized_did = tcx.require_lang_item(hir::LangItem::MetaSized, span);
let pointee_sized_did = tcx.require_lang_item(hir::LangItem::PointeeSized, span);
// If adding sizedness bounds to a trait, then there are some relevant early exits
if let Some(trait_did) = trait_did {
@@ -209,9 +191,22 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
return;
}
} else {
// Report invalid unbounds on sizedness-bounded generic parameters.
let unbounds = collect_unbounds(hir_bounds, self_ty_where_predicates);
self.check_and_report_invalid_unbounds_on_param(unbounds);
// Report invalid relaxed bounds.
// FIXME: Since we only call this validation function here in this function, we only
// fully validate relaxed bounds in contexts where we perform
// "sized elaboration". In most cases that doesn't matter because we *usually*
// reject such relaxed bounds outright during AST lowering.
// However, this can easily get out of sync! Ideally, we would perform this step
// 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 e.g., trait object tys or
// supertrait bounds!
// FIXME(trait_alias, #143122): We don't call it for the RHS. Arguably however,
// AST lowering should reject them outright.
// FIXME(associated_type_bounds): We don't call this for them. However, AST
// lowering should reject them outright (#135229).
let bounds = collect_relaxed_bounds(hir_bounds, self_ty_where_predicates);
self.check_and_report_invalid_relaxed_bounds(bounds);
}
let collected = collect_sizedness_bounds(tcx, hir_bounds, self_ty_where_predicates, span);
@@ -231,7 +226,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
} else {
// If there are no explicit sizedness bounds on a parameter then add a default
// `Sized` bound.
let sized_did = tcx.require_lang_item(LangItem::Sized, span);
let sized_did = tcx.require_lang_item(hir::LangItem::Sized, span);
add_trait_bound(tcx, bounds, self_ty, sized_did, span);
}
}
@@ -463,10 +458,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
'tcx: 'hir,
{
for hir_bound in hir_bounds {
if self.should_skip_sizedness_bound(hir_bound) {
continue;
}
// In order to avoid cycles, when we're lowering `SelfTraitThatDefines`,
// we skip over any traits that don't define the given associated type.
if let PredicateFilter::SelfTraitThatDefines(assoc_ident) = predicate_filter {
@@ -482,12 +473,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
match hir_bound {
hir::GenericBound::Trait(poly_trait_ref) => {
let hir::TraitBoundModifiers { constness, polarity } = poly_trait_ref.modifiers;
let _ = self.lower_poly_trait_ref(
&poly_trait_ref.trait_ref,
poly_trait_ref.span,
constness,
polarity,
poly_trait_ref,
param_ty,
bounds,
predicate_filter,

View File

@@ -2,7 +2,6 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_errors::codes::*;
use rustc_errors::struct_span_code_err;
use rustc_hir as hir;
use rustc_hir::LangItem;
use rustc_hir::def::{DefKind, Res};
use rustc_lint_defs::builtin::UNUSED_ASSOCIATED_TYPE_BOUNDS;
use rustc_middle::ty::elaborate::ClauseWithSupertraitSpan;
@@ -18,9 +17,7 @@ use tracing::{debug, instrument};
use super::HirTyLowerer;
use crate::errors::SelfInTypeAlias;
use crate::hir_ty_lowering::{
GenericArgCountMismatch, GenericArgCountResult, PredicateFilter, RegionInferReason,
};
use crate::hir_ty_lowering::{GenericArgCountMismatch, PredicateFilter, RegionInferReason};
impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// Lower a trait object type from the HIR to our internal notion of a type.
@@ -38,24 +35,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
let mut user_written_bounds = Vec::new();
let mut potential_assoc_types = Vec::new();
for trait_bound in hir_bounds.iter() {
if let hir::BoundPolarity::Maybe(_) = trait_bound.modifiers.polarity {
continue;
}
if let GenericArgCountResult {
correct:
Err(GenericArgCountMismatch { invalid_args: cur_potential_assoc_types, .. }),
..
} = self.lower_poly_trait_ref(
&trait_bound.trait_ref,
trait_bound.span,
trait_bound.modifiers.constness,
hir::BoundPolarity::Positive,
for poly_trait_ref in hir_bounds.iter() {
let result = self.lower_poly_trait_ref(
poly_trait_ref,
dummy_self,
&mut user_written_bounds,
PredicateFilter::SelfOnly,
) {
potential_assoc_types.extend(cur_potential_assoc_types);
);
if let Err(GenericArgCountMismatch { invalid_args, .. }) = result.correct {
potential_assoc_types.extend(invalid_args);
}
}
@@ -81,13 +69,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
let guar = self.report_trait_object_addition_traits(&regular_traits);
return Ty::new_error(tcx, guar);
}
// We don't support `PointeeSized` principals
let pointee_sized_did = tcx.require_lang_item(LangItem::PointeeSized, span);
if regular_traits.iter().any(|(pred, _)| pred.def_id() == pointee_sized_did) {
let guar = self.report_pointee_sized_trait_object(span);
return Ty::new_error(tcx, guar);
}
// Don't create a dyn trait if we have errors in the principal.
if let Err(guar) = regular_traits.error_reported() {
return Ty::new_error(tcx, guar);

View File

@@ -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, LangItem, PolyTraitRef};
use rustc_hir::{self as hir, HirId, PolyTraitRef};
use rustc_middle::bug;
use rustc_middle::ty::fast_reject::{TreatParams, simplify_type};
use rustc_middle::ty::print::{PrintPolyTraitRefExt as _, PrintTraitRefExt as _};
@@ -29,59 +29,54 @@ use tracing::debug;
use super::InherentAssocCandidate;
use crate::errors::{
self, AssocItemConstraintsNotAllowedHere, ManualImplementation, MissingTypeParams,
ParenthesizedFnTraitExpansion, PointeeSizedTraitObject, TraitObjectDeclaredWithNoTraits,
ParenthesizedFnTraitExpansion, TraitObjectDeclaredWithNoTraits,
};
use crate::fluent_generated as fluent;
use crate::hir_ty_lowering::{AssocItemQSelf, HirTyLowerer};
impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// Check for multiple relaxed default bounds and relaxed bounds of non-sizedness traits.
pub(crate) fn check_and_report_invalid_unbounds_on_param(
/// Check for duplicate relaxed bounds and relaxed bounds of non-default traits.
pub(crate) fn check_and_report_invalid_relaxed_bounds(
&self,
unbounds: SmallVec<[&PolyTraitRef<'_>; 1]>,
relaxed_bounds: SmallVec<[&PolyTraitRef<'_>; 1]>,
) {
let tcx = self.tcx();
let sized_did = tcx.require_lang_item(LangItem::Sized, DUMMY_SP);
let mut grouped_bounds = FxIndexMap::<_, Vec<_>>::default();
let mut unique_bounds = FxIndexSet::default();
let mut seen_repeat = false;
for unbound in &unbounds {
if let Res::Def(DefKind::Trait, unbound_def_id) = unbound.trait_ref.path.res {
seen_repeat |= !unique_bounds.insert(unbound_def_id);
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);
}
}
if unbounds.len() > 1 {
let err = errors::MultipleRelaxedDefaultBounds {
spans: unbounds.iter().map(|ptr| ptr.span).collect(),
};
if seen_repeat {
tcx.dcx().emit_err(err);
} else if !tcx.features().more_maybe_bounds() {
tcx.sess.create_feature_err(err, sym::more_maybe_bounds).emit();
};
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();
}
}
for unbound in unbounds {
if let Res::Def(DefKind::Trait, did) = unbound.trait_ref.path.res
&& ((did == sized_did) || tcx.is_default_trait(did))
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;
}
let unbound_traits = match tcx.sess.opts.unstable_opts.experimental_default_bounds {
true => "`?Sized` and `experimental_default_bounds`",
false => "`?Sized`",
};
self.dcx().span_err(
unbound.span,
format!(
"relaxing a default bound only does something for {}; all other traits are \
not bound by default",
unbound_traits
),
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`"
},
);
}
}
@@ -1410,10 +1405,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
self.dcx().emit_err(TraitObjectDeclaredWithNoTraits { span, trait_alias_span })
}
pub(super) fn report_pointee_sized_trait_object(&self, span: Span) -> ErrorGuaranteed {
self.dcx().emit_err(PointeeSizedTraitObject { span })
}
}
/// Emit an error for the given associated item constraint.

View File

@@ -747,18 +747,46 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// If for example you had `for<'a> Foo<'a>: Bar<'a>`, then the `self_ty` would be `Foo<'a>`
/// where `'a` is a bound region at depth 0. Similarly, the `trait_ref` would be `Bar<'a>`.
/// The lowered poly-trait-ref will track this binder explicitly, however.
#[instrument(level = "debug", skip(self, span, constness, bounds))]
#[instrument(level = "debug", skip(self, bounds))]
pub(crate) fn lower_poly_trait_ref(
&self,
trait_ref: &hir::TraitRef<'tcx>,
span: Span,
constness: hir::BoundConstness,
polarity: hir::BoundPolarity,
poly_trait_ref: &hir::PolyTraitRef<'tcx>,
self_ty: Ty<'tcx>,
bounds: &mut Vec<(ty::Clause<'tcx>, Span)>,
predicate_filter: PredicateFilter,
) -> GenericArgCountResult {
let tcx = self.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 } =
*poly_trait_ref;
let hir::TraitBoundModifiers { constness, polarity } = modifiers;
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
// 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) =>
{
// 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())
}
rustc_ast::BoundPolarity::Positive => (ty::PredicatePolarity::Positive, bounds),
rustc_ast::BoundPolarity::Negative(_) => (ty::PredicatePolarity::Negative, bounds),
rustc_ast::BoundPolarity::Maybe(_) => {
(ty::PredicatePolarity::Positive, &mut Vec::new())
}
};
let trait_segment = trait_ref.path.segments.last().unwrap();
let _ = self.prohibit_generic_args(
@@ -775,7 +803,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
Some(self_ty),
);
let tcx = self.tcx();
let bound_vars = tcx.late_bound_vars(trait_ref.hir_ref_id);
debug!(?bound_vars);
@@ -786,27 +813,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
debug!(?poly_trait_ref);
let polarity = match polarity {
rustc_ast::BoundPolarity::Positive => ty::PredicatePolarity::Positive,
rustc_ast::BoundPolarity::Negative(_) => ty::PredicatePolarity::Negative,
rustc_ast::BoundPolarity::Maybe(_) => {
// Validate associated type at least. We may want to reject these
// outright in the future...
for constraint in trait_segment.args().constraints {
let _ = self.lower_assoc_item_constraint(
trait_ref.hir_ref_id,
poly_trait_ref,
constraint,
&mut Default::default(),
&mut Default::default(),
constraint.span,
predicate_filter,
);
}
return arg_count;
}
};
// We deal with const conditions later.
match predicate_filter {
PredicateFilter::All
@@ -909,7 +915,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
// 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!
if polarity != ty::PredicatePolarity::Positive {
if polarity == ty::PredicatePolarity::Negative {
self.dcx().span_delayed_bug(
constraint.span,
"negative trait bounds should not have assoc item constraints",