Skip to main content

rustdoc/clean/
simplify.rs

1//! Simplification of where-clauses and parameter bounds into a prettier and
2//! more canonical form.
3//!
4//! Currently all cross-crate-inlined function use `rustc_middle::ty` to reconstruct
5//! the AST (e.g., see all of `clean::inline`), but this is not always a
6//! non-lossy transformation. The current format of storage for where-clauses
7//! for functions and such is simply a list of predicates. One example of this
8//! is that the AST predicate of: `where T: Trait<Foo = Bar>` is encoded as:
9//! `where T: Trait, <T as Trait>::Foo = Bar`.
10//!
11//! This module attempts to reconstruct the original where and/or parameter
12//! bounds by special casing scenarios such as these. Fun!
13
14use rustc_data_structures::fx::FxIndexMap;
15use rustc_data_structures::thin_vec::ThinVec;
16use rustc_hir as hir;
17use rustc_hir::attrs::lang_items::LangItem;
18use rustc_hir::def_id::DefId;
19use rustc_middle::ty::{TyCtxt, Unnormalized};
20
21use crate::clean;
22use crate::clean::{GenericArgs as PP, WherePredicate as WP};
23use crate::core::DocContext;
24
25pub(crate) fn where_clauses(tcx: TyCtxt<'_>, clauses: ThinVec<WP>) -> ThinVec<WP> {
26    // First, partition the where clause into its separate components.
27    //
28    // We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to
29    // the order of the generated bounds.
30    let mut tybounds = FxIndexMap::default();
31    let mut lifetimes = Vec::new();
32    let mut equalities = Vec::new();
33
34    for clause in clauses {
35        match clause {
36            WP::BoundPredicate { ty, bounds, bound_params } => {
37                let (b, p): &mut (Vec<_>, Vec<_>) = tybounds.entry(ty).or_default();
38                b.extend(bounds);
39                p.extend(bound_params);
40            }
41            WP::RegionPredicate { lifetime, bounds } => {
42                lifetimes.push((lifetime, bounds));
43            }
44            WP::ProjectionPredicate { lhs, rhs } => equalities.push((lhs, rhs)),
45        }
46    }
47
48    // Look for equality predicates on associated types that can be merged into
49    // general bound predicates.
50    equalities.retain(|(lhs, rhs)| {
51        let Some((bounds, _)) = tybounds.get_mut(&lhs.self_type) else { return true };
52        merge_bounds(tcx, bounds, lhs.trait_.as_ref().unwrap().def_id(), lhs.assoc.clone(), rhs)
53    });
54
55    // And finally, let's reassemble everything
56    let mut clauses = ThinVec::with_capacity(lifetimes.len() + tybounds.len() + equalities.len());
57    clauses.extend(
58        lifetimes.into_iter().map(|(lt, bounds)| WP::RegionPredicate { lifetime: lt, bounds }),
59    );
60    clauses.extend(tybounds.into_iter().map(|(ty, (bounds, bound_params))| WP::BoundPredicate {
61        ty,
62        bounds,
63        bound_params,
64    }));
65    clauses.extend(equalities.into_iter().map(|(lhs, rhs)| WP::ProjectionPredicate { lhs, rhs }));
66    clauses
67}
68
69pub(crate) fn merge_bounds(
70    tcx: TyCtxt<'_>,
71    bounds: &mut [clean::GenericBound],
72    trait_did: DefId,
73    assoc: clean::PathSegment,
74    rhs: &clean::Term,
75) -> bool {
76    !bounds.iter_mut().any(|b| {
77        let trait_ref = match *b {
78            clean::GenericBound::TraitBound(ref mut tr, _) => tr,
79            clean::GenericBound::Outlives(..) | clean::GenericBound::Use(_) => return false,
80        };
81        // If this QPath's trait `trait_did` is the same as, or a supertrait
82        // of, the bound's trait `did` then we can keep going, otherwise
83        // this is just a plain old equality bound.
84        if !trait_is_same_or_supertrait(tcx, trait_ref.trait_.def_id(), trait_did) {
85            return false;
86        }
87        let last = trait_ref.trait_.segments.last_mut().expect("segments were empty");
88
89        match last.args {
90            PP::AngleBracketed { ref mut constraints, .. } => {
91                constraints.push(clean::AssocItemConstraint {
92                    assoc: assoc.clone(),
93                    kind: clean::AssocItemConstraintKind::Equality { term: rhs.clone() },
94                });
95            }
96            PP::Parenthesized { ref mut output, .. } => match output {
97                Some(o) => assert_eq!(&clean::Term::Type(o.as_ref().clone()), rhs),
98                None => {
99                    if *rhs != clean::Term::Type(clean::Type::Tuple(Vec::new())) {
100                        *output = Some(Box::new(rhs.ty().unwrap().clone()));
101                    }
102                }
103            },
104            PP::ReturnTypeNotation => {
105                // Cannot merge bounds with RTN.
106                return false;
107            }
108        };
109        true
110    })
111}
112
113fn trait_is_same_or_supertrait(tcx: TyCtxt<'_>, child: DefId, trait_: DefId) -> bool {
114    if child == trait_ {
115        return true;
116    }
117    let clauses = tcx.explicit_super_clauses_of(child);
118    clauses
119        .iter_identity_copied()
120        .map(Unnormalized::skip_norm_wip)
121        .filter_map(|(clause, _)| Some(clause.as_trait_clause()?.def_id()))
122        .any(|did| trait_is_same_or_supertrait(tcx, did, trait_))
123}
124
125/// Reconstruct all sizedness bounds on non-`Self` type parameters as they appear in the surface
126/// language given generics that were cleaned from the middle::ty IR.
127///
128/// For example, assuming `T` is a type parameter of the owner of `generics`,
129/// `T: Sized` gets dropped and `T: MetaSized` gets rewritten to `T: ?Sized`.
130pub(crate) fn sizedness_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generics) {
131    #[derive(PartialEq, Eq, PartialOrd, Ord)]
132    enum Sizedness {
133        PointeeSized,
134        MetaSized,
135        Sized,
136    }
137
138    let mut type_params: FxIndexMap<_, _> = generics
139        .params
140        .iter()
141        .filter(|param| matches!(param.kind, clean::GenericParamDefKind::Type { .. }))
142        .map(|param| (param.name, Sizedness::PointeeSized))
143        .collect();
144
145    generics.where_predicates.retain(|pred| {
146        let WP::BoundPredicate { ty: clean::Generic(param), bounds, .. } = pred else {
147            return true;
148        };
149
150        // We require the caller to pass generics that were cleaned from the middle::ty IR.
151        // We know that that cleaning process never generates more than one bound per predicate.
152        let [bound] = &*bounds else { unreachable!() };
153
154        let clean::GenericBound::TraitBound(trait_ref, hir::TraitBoundModifiers::NONE) = bound
155        else {
156            return true;
157        };
158
159        // This transformation is only valid on type parameters defined on the closest item.
160        // If the parameter was defined by the parent item we know that the sizedness bound
161        // *has* to be user-written in which case we have to preserve it as is.
162        let Some(param_sizedness) = type_params.get_mut(param) else { return true };
163
164        let sizedness = match cx.tcx.as_lang_item(trait_ref.trait_.def_id()) {
165            Some(LangItem::Sized) => Sizedness::Sized,
166            Some(LangItem::MetaSized) => Sizedness::MetaSized,
167            _ => return true,
168        };
169
170        if sizedness > *param_sizedness {
171            *param_sizedness = sizedness;
172        }
173
174        false
175    });
176
177    for (param, sizedness) in type_params {
178        generics.where_predicates.push(WP::BoundPredicate {
179            ty: clean::Type::Generic(param),
180            bounds: vec![match sizedness {
181                // FIXME(sized-hierarchy, #157247): Actually render `MetaSized` as `MetaSized` and
182                // `PointeeSized` as `PointeeSized` instead of `?Sized` if the crate enables
183                // `sized_hierarchy` and doesn't set `#![doc(dont_leak…)]`.
184                Sizedness::MetaSized | Sizedness::PointeeSized => {
185                    clean::GenericBound::maybe_sized(cx)
186                }
187                Sizedness::Sized => continue,
188            }],
189            bound_params: Vec::new(),
190        });
191    }
192}
193
194/// Move bounds that are (likely) directly attached to generic parameters from the where-clause to
195/// the respective parameter.
196///
197/// There is no guarantee that this is what the user actually wrote but we have no way of knowing.
198// FIXME(fmease): It'd make a lot of sense to just incorporate this logic into `clean_ty_generics`
199// making every of its users benefit from it.
200pub(crate) fn move_bounds_to_generic_parameters(generics: &mut clean::Generics) {
201    use clean::types::*;
202
203    let mut where_predicates = ThinVec::new();
204    for mut pred in generics.where_predicates.drain(..) {
205        if let WherePredicate::BoundPredicate { ty: Generic(arg), bounds, .. } = &mut pred
206            && let Some(GenericParamDef {
207                kind: GenericParamDefKind::Type { bounds: param_bounds, .. },
208                ..
209            }) = generics.params.iter_mut().find(|param| &param.name == arg)
210        {
211            param_bounds.extend(bounds.drain(..));
212        } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } =
213            &mut pred
214            && let Some(GenericParamDef {
215                kind: GenericParamDefKind::Lifetime { outlives: param_bounds },
216                ..
217            }) = generics.params.iter_mut().find(|param| &param.name == arg)
218        {
219            param_bounds.extend(bounds.drain(..).map(|bound| match bound {
220                GenericBound::Outlives(lifetime) => lifetime,
221                _ => unreachable!(),
222            }));
223        } else {
224            where_predicates.push(pred);
225        }
226    }
227    generics.where_predicates = where_predicates;
228}