Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 220 additions & 2 deletions compiler/rustc_hir_analysis/src/collect/predicates_of.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use hir::Node;
use rustc_data_structures::assert_matches;
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::find_attr;
use rustc_middle::ty::{
self, GenericPredicates, ImplTraitInTraitData, Ty, TyCtxt, TypeVisitable, TypeVisitor, Upcast,
self, Binder, Clause, GenericArgKind, GenericArgs, GenericPredicates, ImplTraitInTraitData, Ty,
TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitable, TypeVisitor, Upcast, UpcastFrom,
};
use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Ident, Span};
Expand All @@ -30,6 +31,13 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic
let mut result = tcx.explicit_predicates_of(def_id);
debug!("predicates_of: explicit_predicates_of({:?}) = {:?}", def_id, result);

if tcx.sess.opts.unstable_opts.strict_projection_item_bounds {
let implied_item_bounds = elaborate_projection_predicates(tcx, result.predicates.to_vec());
result.predicates = tcx
.arena
.alloc_from_iter(result.predicates.into_iter().copied().chain(implied_item_bounds));
}

let inferred_outlives = tcx.inferred_outlives_of(def_id);
if !inferred_outlives.is_empty() {
debug!("predicates_of: inferred_outlives_of({:?}) = {:?}", def_id, inferred_outlives,);
Expand Down Expand Up @@ -76,6 +84,216 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic
debug!("predicates_of({:?}) = {:?}", def_id, result);
result
}
// If the term of projection is a generic param, we try to add implied item bounds to predicates.
fn elaborate_projection_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
predicates: Vec<(Clause<'tcx>, Span)>,
) -> Vec<(ty::Clause<'tcx>, Span)> {
// We use a folder rather than instantiation because we need to map `Self::Assoc` to the rhs of
// the projection. Instantiation doesn't do that.
// We need to support higher ranked region and friends in the item bounds.
// However, we can't bind item bounds predicates twice. So we just append the bound vars of each item
// bound to the projection predicate binder and shift bound var indices in this folder.
// See `tests/ui/wf/wf-item-bounds-on-projection-with-binder.rs`.
struct PredicateArgFolder<'tcx> {
tcx: TyCtxt<'tcx>,
ty_mapping: FxHashMap<Ty<'tcx>, Ty<'tcx>>,
region_mapping: FxHashMap<ty::Region<'tcx>, ty::Region<'tcx>>,
const_mapping: FxHashMap<ty::Const<'tcx>, ty::Const<'tcx>>,
bound_num: usize,
current_index: ty::DebruijnIndex,
}
impl<'tcx> PredicateArgFolder<'tcx> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this folder differ from whatever.instantiate(projection.projection_term.args)? 🤔

Copy link
Contributor Author

@adwinwhite adwinwhite Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This folder additionally maps identity projection alias to the rhs type of the projection: Self::Assoc -> projection.term. It's needed to apply the item bounds on projection.term.

E.g.

trait Trait<T> {
    type Assoc: Bound<T>;
}

fn function<T, U, I>()
where 
    // We need to transform `Self::Assoc: Bound<T>` into `U: Bound<I>`.
    // With instantiating alone, the result would be `T::Assoc: Bound<I>`, which is pointless 
    // as `AliasBound` candidate already covers this.
    T: Trait<I, Assoc = U>
{}

fn new(tcx: TyCtxt<'tcx>, projection: Binder<'tcx, ty::ProjectionPredicate<'tcx>>) -> Self {
let bound_num = projection.bound_vars().len();
let projection = projection.skip_binder();
let mut ty_mapping = FxHashMap::default();
let mut region_mapping = FxHashMap::default();
let mut const_mapping = FxHashMap::default();
let assoc_ty = Ty::new_alias(
tcx,
ty::AliasTyKind::Projection,
ty::AliasTy::new(
tcx,
projection.projection_term.def_id,
GenericArgs::identity_for_item(tcx, projection.projection_term.def_id),
),
);
ty_mapping.insert(assoc_ty, projection.term.expect_type());
let target_assoc_args =
GenericArgs::identity_for_item(tcx, projection.projection_term.def_id);
for (target_arg, proj_arg) in
target_assoc_args.into_iter().zip(projection.projection_term.args)
{
match (target_arg.kind(), proj_arg.kind()) {
(GenericArgKind::Lifetime(r1), GenericArgKind::Lifetime(r2)) => {
region_mapping.insert(r1, r2);
}
(GenericArgKind::Type(t1), GenericArgKind::Type(t2)) => {
ty_mapping.insert(t1, t2);
}
(GenericArgKind::Const(c1), GenericArgKind::Const(c2)) => {
const_mapping.insert(c1, c2);
}
_ => bug!("mismatched generic arg kinds in projection predicate"),
}
}
debug!(
"elaborate_projection_predicates: ty_mapping = {:?}, region_mapping = {:?}, const_mapping = {:?}",
ty_mapping, region_mapping, const_mapping
);
Self {
tcx,
ty_mapping,
region_mapping,
const_mapping,
bound_num,
current_index: ty::INNERMOST,
}
}
}
impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for PredicateArgFolder<'tcx> {
fn cx(&self) -> TyCtxt<'tcx> {
self.tcx
}

fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
if let ty::Bound(ty::BoundVarIndexKind::Bound(debruijn), bound_ty) = t.kind()
&& *debruijn == self.current_index
{
let shifted_bound_var =
ty::BoundVar::from_usize(bound_ty.var.index() + self.bound_num);
return Ty::new_bound(
self.tcx,
*debruijn,
ty::BoundTy { var: shifted_bound_var, kind: bound_ty.kind },
);
}
if let Some(replacement) = self.ty_mapping.get(&t) {
*replacement
} else {
t.super_fold_with(self)
}
}

fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
if let ty::RegionKind::ReBound(ty::BoundVarIndexKind::Bound(debruijn), bound_re) =
r.kind()
&& debruijn == self.current_index
{
let shifted_bound_var =
ty::BoundVar::from_usize(bound_re.var.index() + self.bound_num);
return ty::Region::new_bound(
self.tcx,
debruijn,
ty::BoundRegion { var: shifted_bound_var, kind: bound_re.kind },
);
}
if let Some(replacement) = self.region_mapping.get(&r) { *replacement } else { r }
}

fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
if let ty::ConstKind::Bound(ty::BoundVarIndexKind::Bound(debruijn), bound_ct) = c.kind()
&& debruijn == self.current_index
{
let shifted_bound_var =
ty::BoundVar::from_usize(bound_ct.var.index() + self.bound_num);
return ty::Const::new_bound(
self.tcx,
debruijn,
ty::BoundConst::new(shifted_bound_var),
);
}
if let Some(replacement) = self.const_mapping.get(&c) { *replacement } else { c }
}

fn fold_binder<T: TypeFoldable<TyCtxt<'tcx>>>(
&mut self,
t: Binder<'tcx, T>,
) -> Binder<'tcx, T> {
self.current_index.shift_in(1);
let folded = t.super_fold_with(self);
self.current_index.shift_out(1);
folded
}
}

let mut new_preds = Vec::new();
for (pred, _span) in &predicates {
if let ty::ClauseKind::Projection(proj_pred) = pred.kind().skip_binder()
&& let Some(proj_ty) = proj_pred.term.as_type()
{
// We should minimize this to allow the where clause check to be useful.
fn should_add_clause(t: Ty<'_>) -> bool {
match t.kind() {
ty::Param(_) => true,
ty::Alias(ty::Projection, alias) => alias.args.types().any(should_add_clause),
_ => false,
}
}
if !should_add_clause(proj_ty) {
continue;
}
debug!("elaborate_projection_predicates: projection predicate = {:?}", pred);
let assoc_bounds = tcx.explicit_item_bounds(proj_pred.projection_term.def_id);
debug!("elaborate_projection_predicates: original assoc_bounds = {:?}", assoc_bounds);
let mut folder = PredicateArgFolder::new(tcx, pred.kind().rebind(proj_pred));
// FIXME: optimize allocation later.
let assoc_bounds: Vec<_> = assoc_bounds
.skip_binder()
.iter()
.map(|(c, span)| {
let concated_bound_vars: Vec<_> =
pred.kind().bound_vars().iter().chain(c.kind().bound_vars()).collect();
debug!(
"elaborate_projection_predicates: concated_bound_vars = {:?}",
concated_bound_vars
);
(
Binder::bind_with_vars(
c.kind().skip_binder().fold_with(&mut folder),
tcx.mk_bound_variable_kinds(&concated_bound_vars),
),
*span,
)
})
.filter(|(c, _)| {
if let ty::ClauseKind::Projection(p) = c.skip_binder() {
if p.projection_term.to_term(tcx) == p.term {
// No need to add identity projection.
// They cause cycles later.
return false;
}
// We shouldn't add opposite projection of existing ones.
// They will be normalized to identity projection by each other.
// We also filter out projections which have the same lhs with existing
// projections.
// They cause ambiguity in normalization.
if predicates.iter().any(|(existing_c, _)| {
if let ty::ClauseKind::Projection(existing_p) =
existing_c.kind().skip_binder()
{
return p.projection_term == existing_p.projection_term
|| (existing_p.projection_term.to_term(tcx) == p.term
&& existing_p.term == p.projection_term.to_term(tcx));
}
false
}) {
return false;
}
}
true
})
.collect();
debug!("elaborate_projection_predicates: replaced assoc_bounds = {:?}", assoc_bounds);
let assoc_bounds: Vec<_> =
assoc_bounds.into_iter().map(|(c, s)| (Clause::upcast_from(c, tcx), s)).collect();
debug!("elaborate_projection_predicates: upcasted assoc_bounds = {:?}", assoc_bounds);
new_preds.extend(assoc_bounds);
}
}
new_preds
}

/// Returns a list of user-specified type predicates for the definition with ID `def_id`.
/// N.B., this does not include any implied/inferred constraints.
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2678,6 +2678,8 @@ written to standard error output)"),
"prefer dynamic linking to static linking for staticlibs (default: no)"),
strict_init_checks: bool = (false, parse_bool, [TRACKED],
"control if mem::uninitialized and mem::zeroed panic on more UB"),
strict_projection_item_bounds: bool = (false, parse_bool, [TRACKED],
"check item bounds on projection rhs for wellformedness"),
#[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")]
teach: bool = (false, parse_bool, [TRACKED],
"show extended diagnostic help (default: no)"),
Expand Down
55 changes: 53 additions & 2 deletions compiler/rustc_trait_selection/src/traits/wf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,7 @@ pub fn clause_obligations<'tcx>(
wf.add_wf_preds_for_term(ty.into());
}
ty::ClauseKind::Projection(t) => {
wf.add_wf_preds_for_alias_term(t.projection_term);
wf.add_wf_preds_for_term(t.term);
wf.add_wf_preds_for_projection_pred(t);
}
ty::ClauseKind::ConstArgHasType(ct, ty) => {
wf.add_wf_preds_for_term(ct.into());
Expand Down Expand Up @@ -455,6 +454,57 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
}
}

fn add_wf_preds_for_projection_pred(&mut self, projection_pred: ty::ProjectionPredicate<'tcx>) {
let tcx = self.tcx();
let cause = self.cause(ObligationCauseCode::WellFormed(None));
debug!("add_wf_preds_for_projection_pred {:?}", projection_pred);

// FIXME: share this function with `elaborate_projection_predicates`.
fn is_total_generic(t: Ty<'_>) -> bool {
match t.kind() {
ty::Param(_) => true,
ty::Alias(ty::Projection, alias) => alias.args.types().all(is_total_generic),
_ => false,
}
}
if tcx.sess.opts.unstable_opts.strict_projection_item_bounds {
// Add item bounds to the predicate term.
// If the term is generic, we can skip these item bounds as they're implied by
// `elaborate_projection_predicates`.
if let Some(normalizes_to_ty) = projection_pred.term.as_type()
&& !is_total_generic(normalizes_to_ty)
{
let bounds = tcx.item_bounds(projection_pred.def_id());
debug!("add_wf_preds_for_projection_pred item_bounds={:?}", bounds);
let bound_obligations: Vec<_> = bounds
.instantiate(tcx, projection_pred.projection_term.args)
.iter()
.filter_map(|bound| {
if !bound.has_escaping_bound_vars() {
Some(traits::Obligation::with_depth(
tcx,
cause.clone(),
self.recursion_depth,
self.param_env,
bound,
))
} else {
None
}
})
.collect();
debug!(
"add_wf_preds_for_projection_pred bound_obligations={:?}",
bound_obligations
);
self.out.extend(bound_obligations);
}
}

self.add_wf_preds_for_alias_term(projection_pred.projection_term);
self.add_wf_preds_for_term(projection_pred.term);
}

/// Pushes the obligations required for an alias (except inherent) to be WF
/// into `self.out`.
fn add_wf_preds_for_alias_term(&mut self, data: ty::AliasTerm<'tcx>) {
Expand All @@ -480,6 +530,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
// `i32: Copy`
// ]
let obligations = self.nominal_obligations(data.def_id, data.args);
debug!("add_wf_preds_for_alias_term nominal_obligations={:?}", obligations);
self.out.extend(obligations);

self.add_wf_preds_for_projection_args(data.args);
Expand Down
33 changes: 33 additions & 0 deletions tests/ui/wf/wf-item-bounds-on-projection-concrete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@ compile-flags: -Zstrict-projection-item-bounds

// Projection predicate's WFedness requires that the rhs term satisfy all item bounds defined on the
// associated type.
// Generic types have those bounds implied.

trait Required {}

trait AssocHasBound {
type Assoc: Required;
}

trait Trait<T> {
type Assoc1: AssocHasBound<Assoc = i32>;
//~^ ERROR: the trait bound `i32: Required` is not satisfied [E0277]
type Assoc2: AssocHasBound<Assoc = T>;
type Assoc3: AssocHasBound<Assoc = Self::DummyAssoc>;
type DummyAssoc;
}

fn some_func<T1, T2, U>()
where
T1: AssocHasBound<Assoc = i32>,
//~^ ERROR: type annotations needed [E0284]
T1: AssocHasBound<Assoc = U>,
{}

fn opaque_with_concrete_assoc(_: impl AssocHasBound<Assoc = i32>) {}
//~^ ERROR: the trait bound `i32: Required` is not satisfied [E0277]

fn opaque_with_generic_assoc<T>(_: impl AssocHasBound<Assoc = T>) {}

fn main() {}
Loading
Loading