Skip to content

rustdoc: Support middle::ty associated const equality predicates again #142092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2025
Merged
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
150 changes: 61 additions & 89 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,9 @@ fn clean_where_predicate<'tcx>(
bounds: wrp.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(),
},

hir::WherePredicateKind::EqPredicate(wrp) => WherePredicate::EqPredicate {
lhs: clean_ty(wrp.lhs_ty, cx),
rhs: clean_ty(wrp.rhs_ty, cx).into(),
},
// We should never actually reach this case because these predicates should've already been
// rejected in an earlier compiler pass. This feature isn't fully implemented (#20041).
hir::WherePredicateKind::EqPredicate(_) => bug!("EqPredicate"),
})
}

Expand Down Expand Up @@ -470,68 +469,55 @@ fn clean_projection_predicate<'tcx>(
cx: &mut DocContext<'tcx>,
) -> WherePredicate {
WherePredicate::EqPredicate {
lhs: clean_projection(
pred.map_bound(|p| {
// FIXME: This needs to be made resilient for `AliasTerm`s that
// are associated consts.
p.projection_term.expect_ty(cx.tcx)
}),
cx,
None,
),
lhs: clean_projection(pred.map_bound(|p| p.projection_term), cx, None),
rhs: clean_middle_term(pred.map_bound(|p| p.term), cx),
}
}

fn clean_projection<'tcx>(
ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>,
proj: ty::Binder<'tcx, ty::AliasTerm<'tcx>>,
cx: &mut DocContext<'tcx>,
def_id: Option<DefId>,
) -> Type {
if cx.tcx.is_impl_trait_in_trait(ty.skip_binder().def_id) {
return clean_middle_opaque_bounds(cx, ty.skip_binder().def_id, ty.skip_binder().args);
}

parent_def_id: Option<DefId>,
) -> QPathData {
let trait_ = clean_trait_ref_with_constraints(
cx,
ty.map_bound(|ty| ty.trait_ref(cx.tcx)),
proj.map_bound(|proj| proj.trait_ref(cx.tcx)),
ThinVec::new(),
);
let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None, None);
let self_def_id = if let Some(def_id) = def_id {
cx.tcx.opt_parent(def_id).or(Some(def_id))
} else {
self_type.def_id(&cx.cache)
let self_type = clean_middle_ty(proj.map_bound(|proj| proj.self_ty()), cx, None, None);
let self_def_id = match parent_def_id {
Some(parent_def_id) => cx.tcx.opt_parent(parent_def_id).or(Some(parent_def_id)),
None => self_type.def_id(&cx.cache),
};
let should_show_cast = compute_should_show_cast(self_def_id, &trait_, &self_type);
Type::QPath(Box::new(QPathData {
assoc: projection_to_path_segment(ty, cx),
should_show_cast,
let should_fully_qualify = should_fully_qualify_path(self_def_id, &trait_, &self_type);

QPathData {
assoc: projection_to_path_segment(proj, cx),
self_type,
should_fully_qualify,
trait_: Some(trait_),
}))
}
}

fn compute_should_show_cast(self_def_id: Option<DefId>, trait_: &Path, self_type: &Type) -> bool {
fn should_fully_qualify_path(self_def_id: Option<DefId>, trait_: &Path, self_type: &Type) -> bool {
!trait_.segments.is_empty()
&& self_def_id
.zip(Some(trait_.def_id()))
.map_or(!self_type.is_self_type(), |(id, trait_)| id != trait_)
}

fn projection_to_path_segment<'tcx>(
ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>,
proj: ty::Binder<'tcx, ty::AliasTerm<'tcx>>,
cx: &mut DocContext<'tcx>,
) -> PathSegment {
let def_id = ty.skip_binder().def_id;
let item = cx.tcx.associated_item(def_id);
let def_id = proj.skip_binder().def_id;
let generics = cx.tcx.generics_of(def_id);
PathSegment {
name: item.name(),
name: cx.tcx.item_name(def_id),
args: GenericArgs::AngleBracketed {
args: clean_middle_generic_args(
cx,
ty.map_bound(|ty| &ty.args[generics.parent_count..]),
proj.map_bound(|ty| &ty.args[generics.parent_count..]),
false,
def_id,
),
Expand Down Expand Up @@ -845,7 +831,7 @@ fn clean_ty_generics<'tcx>(
.predicates
.iter()
.flat_map(|(pred, _)| {
let mut projection = None;
let mut proj_pred = None;
let param_idx = {
let bound_p = pred.kind();
match bound_p.skip_binder() {
Expand All @@ -860,7 +846,7 @@ fn clean_ty_generics<'tcx>(
ty::ClauseKind::Projection(p)
if let ty::Param(param) = p.projection_term.self_ty().kind() =>
{
projection = Some(bound_p.rebind(p));
proj_pred = Some(bound_p.rebind(p));
Some(param.index)
}
_ => None,
Expand All @@ -874,22 +860,12 @@ fn clean_ty_generics<'tcx>(

bounds.extend(pred.get_bounds().into_iter().flatten().cloned());

if let Some(proj) = projection
&& let lhs = clean_projection(
proj.map_bound(|p| {
// FIXME: This needs to be made resilient for `AliasTerm`s that
// are associated consts.
p.projection_term.expect_ty(cx.tcx)
}),
cx,
None,
)
&& let Some((_, trait_did, name)) = lhs.projection()
{
if let Some(pred) = proj_pred {
let lhs = clean_projection(pred.map_bound(|p| p.projection_term), cx, None);
impl_trait_proj.entry(param_idx).or_default().push((
trait_did,
name,
proj.map_bound(|p| p.term),
lhs.trait_.unwrap().def_id(),
lhs.assoc,
pred.map_bound(|p| p.term),
));
}

Expand Down Expand Up @@ -1695,10 +1671,11 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
register_res(cx, trait_.res);
let self_def_id = DefId::local(qself.hir_id.owner.def_id.local_def_index);
let self_type = clean_ty(qself, cx);
let should_show_cast = compute_should_show_cast(Some(self_def_id), &trait_, &self_type);
let should_fully_qualify =
should_fully_qualify_path(Some(self_def_id), &trait_, &self_type);
Type::QPath(Box::new(QPathData {
assoc: clean_path_segment(p.segments.last().expect("segments were empty"), cx),
should_show_cast,
should_fully_qualify,
self_type,
trait_: Some(trait_),
}))
Expand All @@ -1707,16 +1684,16 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
let ty = lower_ty(cx.tcx, hir_ty);
let self_type = clean_ty(qself, cx);

let (trait_, should_show_cast) = match ty.kind() {
let (trait_, should_fully_qualify) = match ty.kind() {
ty::Alias(ty::Projection, proj) => {
let res = Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id);
let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx);
register_res(cx, trait_.res);
let self_def_id = res.opt_def_id();
let should_show_cast =
compute_should_show_cast(self_def_id, &trait_, &self_type);
let should_fully_qualify =
should_fully_qualify_path(self_def_id, &trait_, &self_type);

(Some(trait_), should_show_cast)
(Some(trait_), should_fully_qualify)
}
ty::Alias(ty::Inherent, _) => (None, false),
// Rustdoc handles `ty::Error`s by turning them into `Type::Infer`s.
Expand All @@ -1726,7 +1703,7 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type

Type::QPath(Box::new(QPathData {
assoc: clean_path_segment(segment, cx),
should_show_cast,
should_fully_qualify,
self_type,
trait_,
}))
Expand Down Expand Up @@ -2145,14 +2122,8 @@ pub(crate) fn clean_middle_ty<'tcx>(
.map(|pb| AssocItemConstraint {
assoc: projection_to_path_segment(
pb.map_bound(|pb| {
pb
// HACK(compiler-errors): Doesn't actually matter what self
// type we put here, because we're only using the GAT's args.
.with_self_ty(cx.tcx, cx.tcx.types.self_param)
pb.with_self_ty(cx.tcx, cx.tcx.types.trait_object_dummy_self)
.projection_term
// FIXME: This needs to be made resilient for `AliasTerm`s
// that are associated consts.
.expect_ty(cx.tcx)
}),
cx,
),
Expand Down Expand Up @@ -2185,18 +2156,25 @@ pub(crate) fn clean_middle_ty<'tcx>(
Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None, None)).collect())
}

ty::Alias(ty::Projection, data) => {
clean_projection(bound_ty.rebind(data), cx, parent_def_id)
ty::Alias(ty::Projection, alias_ty @ ty::AliasTy { def_id, args, .. }) => {
if cx.tcx.is_impl_trait_in_trait(def_id) {
clean_middle_opaque_bounds(cx, def_id, args)
} else {
Type::QPath(Box::new(clean_projection(
bound_ty.rebind(alias_ty.into()),
cx,
parent_def_id,
)))
}
}

ty::Alias(ty::Inherent, alias_ty) => {
let def_id = alias_ty.def_id;
ty::Alias(ty::Inherent, alias_ty @ ty::AliasTy { def_id, .. }) => {
let alias_ty = bound_ty.rebind(alias_ty);
let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None);

Type::QPath(Box::new(QPathData {
assoc: PathSegment {
name: cx.tcx.associated_item(def_id).name(),
name: cx.tcx.item_name(def_id),
args: GenericArgs::AngleBracketed {
args: clean_middle_generic_args(
cx,
Expand All @@ -2207,26 +2185,21 @@ pub(crate) fn clean_middle_ty<'tcx>(
constraints: Default::default(),
},
},
should_show_cast: false,
should_fully_qualify: false,
self_type,
trait_: None,
}))
}

ty::Alias(ty::Free, data) => {
ty::Alias(ty::Free, ty::AliasTy { def_id, args, .. }) => {
if cx.tcx.features().lazy_type_alias() {
// Free type alias `data` represents the `type X` in `type X = Y`. If we need `Y`,
// we need to use `type_of`.
let path = clean_middle_path(
cx,
data.def_id,
false,
ThinVec::new(),
bound_ty.rebind(data.args),
);
let path =
clean_middle_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(args));
Type::Path { path }
} else {
let ty = cx.tcx.type_of(data.def_id).instantiate(cx.tcx, data.args);
let ty = cx.tcx.type_of(def_id).instantiate(cx.tcx, args);
clean_middle_ty(bound_ty.rebind(ty), cx, None, None)
}
}
Expand Down Expand Up @@ -2313,18 +2286,17 @@ fn clean_middle_opaque_bounds<'tcx>(
let bindings: ThinVec<_> = bounds
.iter()
.filter_map(|(bound, _)| {
if let ty::ClauseKind::Projection(proj) = bound.kind().skip_binder()
&& proj.projection_term.trait_ref(cx.tcx) == trait_ref.skip_binder()
let bound = bound.kind();
if let ty::ClauseKind::Projection(proj_pred) = bound.skip_binder()
&& proj_pred.projection_term.trait_ref(cx.tcx) == trait_ref.skip_binder()
{
return Some(AssocItemConstraint {
assoc: projection_to_path_segment(
// FIXME: This needs to be made resilient for `AliasTerm`s that
// are associated consts.
bound.kind().rebind(proj.projection_term.expect_ty(cx.tcx)),
bound.rebind(proj_pred.projection_term),
cx,
),
kind: AssocItemConstraintKind::Equality {
term: clean_middle_term(bound.kind().rebind(proj.term), cx),
term: clean_middle_term(bound.rebind(proj_pred.term), cx),
},
});
}
Expand Down
7 changes: 2 additions & 5 deletions src/librustdoc/clean/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,8 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: ThinVec<WP>) -> ThinVe
// Look for equality predicates on associated types that can be merged into
// general bound predicates.
equalities.retain(|(lhs, rhs)| {
let Some((ty, trait_did, name)) = lhs.projection() else {
return true;
};
let Some((bounds, _)) = tybounds.get_mut(ty) else { return true };
merge_bounds(cx, bounds, trait_did, name, rhs)
let Some((bounds, _)) = tybounds.get_mut(&lhs.self_type) else { return true };
merge_bounds(cx, bounds, lhs.trait_.as_ref().unwrap().def_id(), lhs.assoc.clone(), rhs)
});

// And finally, let's reassemble everything
Expand Down
12 changes: 2 additions & 10 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,7 @@ impl PreciseCapturingArg {
pub(crate) enum WherePredicate {
BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<GenericParamDef> },
RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> },
EqPredicate { lhs: Type, rhs: Term },
EqPredicate { lhs: QPathData, rhs: Term },
}

impl WherePredicate {
Expand Down Expand Up @@ -1704,14 +1704,6 @@ impl Type {
matches!(self, Type::Tuple(v) if v.is_empty())
}

pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
if let QPath(box QPathData { self_type, trait_, assoc, .. }) = self {
Some((self_type, trait_.as_ref()?.def_id(), assoc.clone()))
} else {
None
}
}

/// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s.
///
/// [clean]: crate::clean
Expand Down Expand Up @@ -1746,7 +1738,7 @@ pub(crate) struct QPathData {
pub assoc: PathSegment,
pub self_type: Type,
/// FIXME: compute this field on demand.
pub should_show_cast: bool,
pub should_fully_qualify: bool,
pub trait_: Option<Path>,
}

Expand Down
Loading
Loading