summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/clean
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/clean')
-rw-r--r--src/librustdoc/clean/auto_trait.rs742
-rw-r--r--src/librustdoc/clean/blanket_impl.rs136
-rw-r--r--src/librustdoc/clean/cfg.rs588
-rw-r--r--src/librustdoc/clean/cfg/tests.rs467
-rw-r--r--src/librustdoc/clean/inline.rs727
-rw-r--r--src/librustdoc/clean/mod.rs2242
-rw-r--r--src/librustdoc/clean/render_macro_matchers.rs238
-rw-r--r--src/librustdoc/clean/simplify.rs139
-rw-r--r--src/librustdoc/clean/types.rs2508
-rw-r--r--src/librustdoc/clean/types/tests.rs70
-rw-r--r--src/librustdoc/clean/utils.rs616
-rw-r--r--src/librustdoc/clean/utils/tests.rs41
12 files changed, 8514 insertions, 0 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
new file mode 100644
index 000000000..af33c1a6a
--- /dev/null
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -0,0 +1,742 @@
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir as hir;
+use rustc_hir::lang_items::LangItem;
+use rustc_middle::ty::{self, Region, RegionVid, TypeFoldable, TypeSuperFoldable};
+use rustc_trait_selection::traits::auto_trait::{self, AutoTraitResult};
+
+use std::fmt::Debug;
+
+use super::*;
+
+#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
+enum RegionTarget<'tcx> {
+ Region(Region<'tcx>),
+ RegionVid(RegionVid),
+}
+
+#[derive(Default, Debug, Clone)]
+struct RegionDeps<'tcx> {
+ larger: FxHashSet<RegionTarget<'tcx>>,
+ smaller: FxHashSet<RegionTarget<'tcx>>,
+}
+
+pub(crate) struct AutoTraitFinder<'a, 'tcx> {
+ pub(crate) cx: &'a mut core::DocContext<'tcx>,
+}
+
+impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx>
+where
+ 'tcx: 'a, // should be an implied bound; rustc bug #98852.
+{
+ pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> Self {
+ AutoTraitFinder { cx }
+ }
+
+ fn generate_for_trait(
+ &mut self,
+ ty: Ty<'tcx>,
+ trait_def_id: DefId,
+ param_env: ty::ParamEnv<'tcx>,
+ item_def_id: DefId,
+ f: &auto_trait::AutoTraitFinder<'tcx>,
+ // If this is set, show only negative trait implementations, not positive ones.
+ discard_positive_impl: bool,
+ ) -> Option<Item> {
+ let tcx = self.cx.tcx;
+ let trait_ref = ty::TraitRef { def_id: trait_def_id, substs: tcx.mk_substs_trait(ty, &[]) };
+ if !self.cx.generated_synthetics.insert((ty, trait_def_id)) {
+ debug!("get_auto_trait_impl_for({:?}): already generated, aborting", trait_ref);
+ return None;
+ }
+
+ let result = f.find_auto_trait_generics(ty, param_env, trait_def_id, |info| {
+ let region_data = info.region_data;
+
+ let names_map = tcx
+ .generics_of(item_def_id)
+ .params
+ .iter()
+ .filter_map(|param| match param.kind {
+ ty::GenericParamDefKind::Lifetime => Some(param.name),
+ _ => None,
+ })
+ .map(|name| (name, Lifetime(name)))
+ .collect();
+ let lifetime_predicates = Self::handle_lifetimes(&region_data, &names_map);
+ let new_generics = self.param_env_to_generics(
+ item_def_id,
+ info.full_user_env,
+ lifetime_predicates,
+ info.vid_to_region,
+ );
+
+ debug!(
+ "find_auto_trait_generics(item_def_id={:?}, trait_def_id={:?}): \
+ finished with {:?}",
+ item_def_id, trait_def_id, new_generics
+ );
+
+ new_generics
+ });
+
+ let polarity;
+ let new_generics = match result {
+ AutoTraitResult::PositiveImpl(new_generics) => {
+ polarity = ty::ImplPolarity::Positive;
+ if discard_positive_impl {
+ return None;
+ }
+ new_generics
+ }
+ AutoTraitResult::NegativeImpl => {
+ polarity = ty::ImplPolarity::Negative;
+
+ // For negative impls, we use the generic params, but *not* the predicates,
+ // from the original type. Otherwise, the displayed impl appears to be a
+ // conditional negative impl, when it's really unconditional.
+ //
+ // For example, consider the struct Foo<T: Copy>(*mut T). Using
+ // the original predicates in our impl would cause us to generate
+ // `impl !Send for Foo<T: Copy>`, which makes it appear that Foo
+ // implements Send where T is not copy.
+ //
+ // Instead, we generate `impl !Send for Foo<T>`, which better
+ // expresses the fact that `Foo<T>` never implements `Send`,
+ // regardless of the choice of `T`.
+ let raw_generics = clean_ty_generics(
+ self.cx,
+ tcx.generics_of(item_def_id),
+ ty::GenericPredicates::default(),
+ );
+ let params = raw_generics.params;
+
+ Generics { params, where_predicates: Vec::new() }
+ }
+ AutoTraitResult::ExplicitImpl => return None,
+ };
+
+ Some(Item {
+ name: None,
+ attrs: Default::default(),
+ visibility: Inherited,
+ item_id: ItemId::Auto { trait_: trait_def_id, for_: item_def_id },
+ kind: Box::new(ImplItem(Box::new(Impl {
+ unsafety: hir::Unsafety::Normal,
+ generics: new_generics,
+ trait_: Some(clean_trait_ref_with_bindings(self.cx, trait_ref, &[])),
+ for_: clean_middle_ty(ty, self.cx, None),
+ items: Vec::new(),
+ polarity,
+ kind: ImplKind::Auto,
+ }))),
+ cfg: None,
+ })
+ }
+
+ pub(crate) fn get_auto_trait_impls(&mut self, item_def_id: DefId) -> Vec<Item> {
+ let tcx = self.cx.tcx;
+ let param_env = tcx.param_env(item_def_id);
+ let ty = tcx.type_of(item_def_id);
+ let f = auto_trait::AutoTraitFinder::new(tcx);
+
+ debug!("get_auto_trait_impls({:?})", ty);
+ let auto_traits: Vec<_> = self.cx.auto_traits.iter().copied().collect();
+ let mut auto_traits: Vec<Item> = auto_traits
+ .into_iter()
+ .filter_map(|trait_def_id| {
+ self.generate_for_trait(ty, trait_def_id, param_env, item_def_id, &f, false)
+ })
+ .collect();
+ // We are only interested in case the type *doesn't* implement the Sized trait.
+ if !ty.is_sized(tcx.at(rustc_span::DUMMY_SP), param_env) {
+ // In case `#![no_core]` is used, `sized_trait` returns nothing.
+ if let Some(item) = tcx.lang_items().sized_trait().and_then(|sized_trait_did| {
+ self.generate_for_trait(ty, sized_trait_did, param_env, item_def_id, &f, true)
+ }) {
+ auto_traits.push(item);
+ }
+ }
+ auto_traits
+ }
+
+ fn get_lifetime(region: Region<'_>, names_map: &FxHashMap<Symbol, Lifetime>) -> Lifetime {
+ region_name(region)
+ .map(|name| {
+ names_map.get(&name).unwrap_or_else(|| {
+ panic!("Missing lifetime with name {:?} for {:?}", name.as_str(), region)
+ })
+ })
+ .unwrap_or(&Lifetime::statik())
+ .clone()
+ }
+
+ /// This method calculates two things: Lifetime constraints of the form `'a: 'b`,
+ /// and region constraints of the form `RegionVid: 'a`
+ ///
+ /// This is essentially a simplified version of lexical_region_resolve. However,
+ /// handle_lifetimes determines what *needs be* true in order for an impl to hold.
+ /// lexical_region_resolve, along with much of the rest of the compiler, is concerned
+ /// with determining if a given set up constraints/predicates *are* met, given some
+ /// starting conditions (e.g., user-provided code). For this reason, it's easier
+ /// to perform the calculations we need on our own, rather than trying to make
+ /// existing inference/solver code do what we want.
+ fn handle_lifetimes<'cx>(
+ regions: &RegionConstraintData<'cx>,
+ names_map: &FxHashMap<Symbol, Lifetime>,
+ ) -> Vec<WherePredicate> {
+ // Our goal is to 'flatten' the list of constraints by eliminating
+ // all intermediate RegionVids. At the end, all constraints should
+ // be between Regions (aka region variables). This gives us the information
+ // we need to create the Generics.
+ let mut finished: FxHashMap<_, Vec<_>> = Default::default();
+
+ let mut vid_map: FxHashMap<RegionTarget<'_>, RegionDeps<'_>> = Default::default();
+
+ // Flattening is done in two parts. First, we insert all of the constraints
+ // into a map. Each RegionTarget (either a RegionVid or a Region) maps
+ // to its smaller and larger regions. Note that 'larger' regions correspond
+ // to sub-regions in Rust code (e.g., in 'a: 'b, 'a is the larger region).
+ for constraint in regions.constraints.keys() {
+ match *constraint {
+ Constraint::VarSubVar(r1, r2) => {
+ {
+ let deps1 = vid_map.entry(RegionTarget::RegionVid(r1)).or_default();
+ deps1.larger.insert(RegionTarget::RegionVid(r2));
+ }
+
+ let deps2 = vid_map.entry(RegionTarget::RegionVid(r2)).or_default();
+ deps2.smaller.insert(RegionTarget::RegionVid(r1));
+ }
+ Constraint::RegSubVar(region, vid) => {
+ let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default();
+ deps.smaller.insert(RegionTarget::Region(region));
+ }
+ Constraint::VarSubReg(vid, region) => {
+ let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default();
+ deps.larger.insert(RegionTarget::Region(region));
+ }
+ Constraint::RegSubReg(r1, r2) => {
+ // The constraint is already in the form that we want, so we're done with it
+ // Desired order is 'larger, smaller', so flip then
+ if region_name(r1) != region_name(r2) {
+ finished
+ .entry(region_name(r2).expect("no region_name found"))
+ .or_default()
+ .push(r1);
+ }
+ }
+ }
+ }
+
+ // Here, we 'flatten' the map one element at a time.
+ // All of the element's sub and super regions are connected
+ // to each other. For example, if we have a graph that looks like this:
+ //
+ // (A, B) - C - (D, E)
+ // Where (A, B) are subregions, and (D,E) are super-regions
+ //
+ // then after deleting 'C', the graph will look like this:
+ // ... - A - (D, E ...)
+ // ... - B - (D, E, ...)
+ // (A, B, ...) - D - ...
+ // (A, B, ...) - E - ...
+ //
+ // where '...' signifies the existing sub and super regions of an entry
+ // When two adjacent ty::Regions are encountered, we've computed a final
+ // constraint, and add it to our list. Since we make sure to never re-add
+ // deleted items, this process will always finish.
+ while !vid_map.is_empty() {
+ let target = *vid_map.keys().next().expect("Keys somehow empty");
+ let deps = vid_map.remove(&target).expect("Entry somehow missing");
+
+ for smaller in deps.smaller.iter() {
+ for larger in deps.larger.iter() {
+ match (smaller, larger) {
+ (&RegionTarget::Region(r1), &RegionTarget::Region(r2)) => {
+ if region_name(r1) != region_name(r2) {
+ finished
+ .entry(region_name(r2).expect("no region name found"))
+ .or_default()
+ .push(r1) // Larger, smaller
+ }
+ }
+ (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => {
+ if let Entry::Occupied(v) = vid_map.entry(*smaller) {
+ let smaller_deps = v.into_mut();
+ smaller_deps.larger.insert(*larger);
+ smaller_deps.larger.remove(&target);
+ }
+ }
+ (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => {
+ if let Entry::Occupied(v) = vid_map.entry(*larger) {
+ let deps = v.into_mut();
+ deps.smaller.insert(*smaller);
+ deps.smaller.remove(&target);
+ }
+ }
+ (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => {
+ if let Entry::Occupied(v) = vid_map.entry(*smaller) {
+ let smaller_deps = v.into_mut();
+ smaller_deps.larger.insert(*larger);
+ smaller_deps.larger.remove(&target);
+ }
+
+ if let Entry::Occupied(v) = vid_map.entry(*larger) {
+ let larger_deps = v.into_mut();
+ larger_deps.smaller.insert(*smaller);
+ larger_deps.smaller.remove(&target);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ let lifetime_predicates = names_map
+ .iter()
+ .flat_map(|(name, lifetime)| {
+ let empty = Vec::new();
+ let bounds: FxHashSet<GenericBound> = finished
+ .get(name)
+ .unwrap_or(&empty)
+ .iter()
+ .map(|region| GenericBound::Outlives(Self::get_lifetime(*region, names_map)))
+ .collect();
+
+ if bounds.is_empty() {
+ return None;
+ }
+ Some(WherePredicate::RegionPredicate {
+ lifetime: lifetime.clone(),
+ bounds: bounds.into_iter().collect(),
+ })
+ })
+ .collect();
+
+ lifetime_predicates
+ }
+
+ fn extract_for_generics(&self, pred: ty::Predicate<'tcx>) -> FxHashSet<GenericParamDef> {
+ let bound_predicate = pred.kind();
+ let tcx = self.cx.tcx;
+ let regions = match bound_predicate.skip_binder() {
+ ty::PredicateKind::Trait(poly_trait_pred) => {
+ tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_trait_pred))
+ }
+ ty::PredicateKind::Projection(poly_proj_pred) => {
+ tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_proj_pred))
+ }
+ _ => return FxHashSet::default(),
+ };
+
+ regions
+ .into_iter()
+ .filter_map(|br| {
+ match br {
+ // We only care about named late bound regions, as we need to add them
+ // to the 'for<>' section
+ ty::BrNamed(_, name) => Some(GenericParamDef {
+ name,
+ kind: GenericParamDefKind::Lifetime { outlives: vec![] },
+ }),
+ _ => None,
+ }
+ })
+ .collect()
+ }
+
+ fn make_final_bounds(
+ &self,
+ ty_to_bounds: FxHashMap<Type, FxHashSet<GenericBound>>,
+ ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)>,
+ lifetime_to_bounds: FxHashMap<Lifetime, FxHashSet<GenericBound>>,
+ ) -> Vec<WherePredicate> {
+ ty_to_bounds
+ .into_iter()
+ .flat_map(|(ty, mut bounds)| {
+ if let Some((ref poly_trait, ref output)) = ty_to_fn.get(&ty) {
+ let mut new_path = poly_trait.trait_.clone();
+ let last_segment = new_path.segments.pop().expect("segments were empty");
+
+ let (old_input, old_output) = match last_segment.args {
+ GenericArgs::AngleBracketed { args, .. } => {
+ let types = args
+ .iter()
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty.clone()),
+ _ => None,
+ })
+ .collect();
+ (types, None)
+ }
+ GenericArgs::Parenthesized { inputs, output } => (inputs, output),
+ };
+
+ let output = output.as_ref().cloned().map(Box::new);
+ if old_output.is_some() && old_output != output {
+ panic!("Output mismatch for {:?} {:?} {:?}", ty, old_output, output);
+ }
+
+ let new_params = GenericArgs::Parenthesized { inputs: old_input, output };
+
+ new_path
+ .segments
+ .push(PathSegment { name: last_segment.name, args: new_params });
+
+ bounds.insert(GenericBound::TraitBound(
+ PolyTrait {
+ trait_: new_path,
+ generic_params: poly_trait.generic_params.clone(),
+ },
+ hir::TraitBoundModifier::None,
+ ));
+ }
+ if bounds.is_empty() {
+ return None;
+ }
+
+ let mut bounds_vec = bounds.into_iter().collect();
+ self.sort_where_bounds(&mut bounds_vec);
+
+ Some(WherePredicate::BoundPredicate {
+ ty,
+ bounds: bounds_vec,
+ bound_params: Vec::new(),
+ })
+ })
+ .chain(
+ lifetime_to_bounds.into_iter().filter(|&(_, ref bounds)| !bounds.is_empty()).map(
+ |(lifetime, bounds)| {
+ let mut bounds_vec = bounds.into_iter().collect();
+ self.sort_where_bounds(&mut bounds_vec);
+ WherePredicate::RegionPredicate { lifetime, bounds: bounds_vec }
+ },
+ ),
+ )
+ .collect()
+ }
+
+ /// Converts the calculated `ParamEnv` and lifetime information to a [`clean::Generics`](Generics), suitable for
+ /// display on the docs page. Cleaning the `Predicates` produces sub-optimal [`WherePredicate`]s,
+ /// so we fix them up:
+ ///
+ /// * Multiple bounds for the same type are coalesced into one: e.g., `T: Copy`, `T: Debug`
+ /// becomes `T: Copy + Debug`
+ /// * `Fn` bounds are handled specially - instead of leaving it as `T: Fn(), <T as Fn::Output> =
+ /// K`, we use the dedicated syntax `T: Fn() -> K`
+ /// * We explicitly add a `?Sized` bound if we didn't find any `Sized` predicates for a type
+ fn param_env_to_generics(
+ &mut self,
+ item_def_id: DefId,
+ param_env: ty::ParamEnv<'tcx>,
+ mut existing_predicates: Vec<WherePredicate>,
+ vid_to_region: FxHashMap<ty::RegionVid, ty::Region<'tcx>>,
+ ) -> Generics {
+ debug!(
+ "param_env_to_generics(item_def_id={:?}, param_env={:?}, \
+ existing_predicates={:?})",
+ item_def_id, param_env, existing_predicates
+ );
+
+ let tcx = self.cx.tcx;
+
+ // The `Sized` trait must be handled specially, since we only display it when
+ // it is *not* required (i.e., '?Sized')
+ let sized_trait = tcx.require_lang_item(LangItem::Sized, None);
+
+ let mut replacer = RegionReplacer { vid_to_region: &vid_to_region, tcx };
+
+ let orig_bounds: FxHashSet<_> = tcx.param_env(item_def_id).caller_bounds().iter().collect();
+ let clean_where_predicates = param_env
+ .caller_bounds()
+ .iter()
+ .filter(|p| {
+ !orig_bounds.contains(p)
+ || match p.kind().skip_binder() {
+ ty::PredicateKind::Trait(pred) => pred.def_id() == sized_trait,
+ _ => false,
+ }
+ })
+ .map(|p| p.fold_with(&mut replacer));
+
+ let raw_generics = clean_ty_generics(
+ self.cx,
+ tcx.generics_of(item_def_id),
+ tcx.explicit_predicates_of(item_def_id),
+ );
+ let mut generic_params = raw_generics.params;
+
+ debug!("param_env_to_generics({:?}): generic_params={:?}", item_def_id, generic_params);
+
+ let mut has_sized = FxHashSet::default();
+ let mut ty_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default();
+ let mut lifetime_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default();
+ let mut ty_to_traits: FxHashMap<Type, FxHashSet<Path>> = Default::default();
+
+ let mut ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)> = Default::default();
+
+ for p in clean_where_predicates {
+ let (orig_p, p) = (p, p.clean(self.cx));
+ if p.is_none() {
+ continue;
+ }
+ let p = p.unwrap();
+ match p {
+ WherePredicate::BoundPredicate { ty, mut bounds, .. } => {
+ // Writing a projection trait bound of the form
+ // <T as Trait>::Name : ?Sized
+ // is illegal, because ?Sized bounds can only
+ // be written in the (here, nonexistent) definition
+ // of the type.
+ // Therefore, we make sure that we never add a ?Sized
+ // bound for projections
+ if let Type::QPath { .. } = ty {
+ has_sized.insert(ty.clone());
+ }
+
+ if bounds.is_empty() {
+ continue;
+ }
+
+ let mut for_generics = self.extract_for_generics(orig_p);
+
+ assert!(bounds.len() == 1);
+ let mut b = bounds.pop().expect("bounds were empty");
+
+ if b.is_sized_bound(self.cx) {
+ has_sized.insert(ty.clone());
+ } else if !b
+ .get_trait_path()
+ .and_then(|trait_| {
+ ty_to_traits
+ .get(&ty)
+ .map(|bounds| bounds.contains(&strip_path_generics(trait_)))
+ })
+ .unwrap_or(false)
+ {
+ // If we've already added a projection bound for the same type, don't add
+ // this, as it would be a duplicate
+
+ // Handle any 'Fn/FnOnce/FnMut' bounds specially,
+ // as we want to combine them with any 'Output' qpaths
+ // later
+
+ let is_fn = match b {
+ GenericBound::TraitBound(ref mut p, _) => {
+ // Insert regions into the for_generics hash map first, to ensure
+ // that we don't end up with duplicate bounds (e.g., for<'b, 'b>)
+ for_generics.extend(p.generic_params.clone());
+ p.generic_params = for_generics.into_iter().collect();
+ self.is_fn_trait(&p.trait_)
+ }
+ _ => false,
+ };
+
+ let poly_trait = b.get_poly_trait().expect("Cannot get poly trait");
+
+ if is_fn {
+ ty_to_fn
+ .entry(ty.clone())
+ .and_modify(|e| *e = (poly_trait.clone(), e.1.clone()))
+ .or_insert(((poly_trait.clone()), None));
+
+ ty_to_bounds.entry(ty.clone()).or_default();
+ } else {
+ ty_to_bounds.entry(ty.clone()).or_default().insert(b.clone());
+ }
+ }
+ }
+ WherePredicate::RegionPredicate { lifetime, bounds } => {
+ lifetime_to_bounds.entry(lifetime).or_default().extend(bounds);
+ }
+ WherePredicate::EqPredicate { lhs, rhs } => {
+ match lhs {
+ Type::QPath { ref assoc, ref self_type, ref trait_, .. } => {
+ let ty = &*self_type;
+ let mut new_trait = trait_.clone();
+
+ if self.is_fn_trait(trait_) && assoc.name == sym::Output {
+ ty_to_fn
+ .entry(*ty.clone())
+ .and_modify(|e| {
+ *e = (e.0.clone(), Some(rhs.ty().unwrap().clone()))
+ })
+ .or_insert((
+ PolyTrait {
+ trait_: trait_.clone(),
+ generic_params: Vec::new(),
+ },
+ Some(rhs.ty().unwrap().clone()),
+ ));
+ continue;
+ }
+
+ let args = &mut new_trait
+ .segments
+ .last_mut()
+ .expect("segments were empty")
+ .args;
+
+ match args {
+ // Convert something like '<T as Iterator::Item> = u8'
+ // to 'T: Iterator<Item=u8>'
+ GenericArgs::AngleBracketed { ref mut bindings, .. } => {
+ bindings.push(TypeBinding {
+ assoc: *assoc.clone(),
+ kind: TypeBindingKind::Equality { term: rhs },
+ });
+ }
+ GenericArgs::Parenthesized { .. } => {
+ existing_predicates.push(WherePredicate::EqPredicate {
+ lhs: lhs.clone(),
+ rhs,
+ });
+ continue; // If something other than a Fn ends up
+ // with parentheses, leave it alone
+ }
+ }
+
+ let bounds = ty_to_bounds.entry(*ty.clone()).or_default();
+
+ bounds.insert(GenericBound::TraitBound(
+ PolyTrait { trait_: new_trait, generic_params: Vec::new() },
+ hir::TraitBoundModifier::None,
+ ));
+
+ // Remove any existing 'plain' bound (e.g., 'T: Iterator`) so
+ // that we don't see a
+ // duplicate bound like `T: Iterator + Iterator<Item=u8>`
+ // on the docs page.
+ bounds.remove(&GenericBound::TraitBound(
+ PolyTrait { trait_: trait_.clone(), generic_params: Vec::new() },
+ hir::TraitBoundModifier::None,
+ ));
+ // Avoid creating any new duplicate bounds later in the outer
+ // loop
+ ty_to_traits.entry(*ty.clone()).or_default().insert(trait_.clone());
+ }
+ _ => panic!("Unexpected LHS {:?} for {:?}", lhs, item_def_id),
+ }
+ }
+ };
+ }
+
+ let final_bounds = self.make_final_bounds(ty_to_bounds, ty_to_fn, lifetime_to_bounds);
+
+ existing_predicates.extend(final_bounds);
+
+ for param in generic_params.iter_mut() {
+ match param.kind {
+ GenericParamDefKind::Type { ref mut default, ref mut bounds, .. } => {
+ // We never want something like `impl<T=Foo>`.
+ default.take();
+ let generic_ty = Type::Generic(param.name);
+ if !has_sized.contains(&generic_ty) {
+ bounds.insert(0, GenericBound::maybe_sized(self.cx));
+ }
+ }
+ GenericParamDefKind::Lifetime { .. } => {}
+ GenericParamDefKind::Const { ref mut default, .. } => {
+ // We never want something like `impl<const N: usize = 10>`
+ default.take();
+ }
+ }
+ }
+
+ self.sort_where_predicates(&mut existing_predicates);
+
+ Generics { params: generic_params, where_predicates: existing_predicates }
+ }
+
+ /// Ensure that the predicates are in a consistent order. The precise
+ /// ordering doesn't actually matter, but it's important that
+ /// a given set of predicates always appears in the same order -
+ /// both for visual consistency between 'rustdoc' runs, and to
+ /// make writing tests much easier
+ #[inline]
+ fn sort_where_predicates(&self, predicates: &mut Vec<WherePredicate>) {
+ // We should never have identical bounds - and if we do,
+ // they're visually identical as well. Therefore, using
+ // an unstable sort is fine.
+ self.unstable_debug_sort(predicates);
+ }
+
+ /// Ensure that the bounds are in a consistent order. The precise
+ /// ordering doesn't actually matter, but it's important that
+ /// a given set of bounds always appears in the same order -
+ /// both for visual consistency between 'rustdoc' runs, and to
+ /// make writing tests much easier
+ #[inline]
+ fn sort_where_bounds(&self, bounds: &mut Vec<GenericBound>) {
+ // We should never have identical bounds - and if we do,
+ // they're visually identical as well. Therefore, using
+ // an unstable sort is fine.
+ self.unstable_debug_sort(bounds);
+ }
+
+ /// This might look horrendously hacky, but it's actually not that bad.
+ ///
+ /// For performance reasons, we use several different FxHashMaps
+ /// in the process of computing the final set of where predicates.
+ /// However, the iteration order of a HashMap is completely unspecified.
+ /// In fact, the iteration of an FxHashMap can even vary between platforms,
+ /// since FxHasher has different behavior for 32-bit and 64-bit platforms.
+ ///
+ /// Obviously, it's extremely undesirable for documentation rendering
+ /// to be dependent on the platform it's run on. Apart from being confusing
+ /// to end users, it makes writing tests much more difficult, as predicates
+ /// can appear in any order in the final result.
+ ///
+ /// To solve this problem, we sort WherePredicates and GenericBounds
+ /// by their Debug string. The thing to keep in mind is that we don't really
+ /// care what the final order is - we're synthesizing an impl or bound
+ /// ourselves, so any order can be considered equally valid. By sorting the
+ /// predicates and bounds, however, we ensure that for a given codebase, all
+ /// auto-trait impls always render in exactly the same way.
+ ///
+ /// Using the Debug implementation for sorting prevents us from needing to
+ /// write quite a bit of almost entirely useless code (e.g., how should two
+ /// Types be sorted relative to each other). It also allows us to solve the
+ /// problem for both WherePredicates and GenericBounds at the same time. This
+ /// approach is probably somewhat slower, but the small number of items
+ /// involved (impls rarely have more than a few bounds) means that it
+ /// shouldn't matter in practice.
+ fn unstable_debug_sort<T: Debug>(&self, vec: &mut Vec<T>) {
+ vec.sort_by_cached_key(|x| format!("{:?}", x))
+ }
+
+ fn is_fn_trait(&self, path: &Path) -> bool {
+ let tcx = self.cx.tcx;
+ let did = path.def_id();
+ did == tcx.require_lang_item(LangItem::Fn, None)
+ || did == tcx.require_lang_item(LangItem::FnMut, None)
+ || did == tcx.require_lang_item(LangItem::FnOnce, None)
+ }
+}
+
+fn region_name(region: Region<'_>) -> Option<Symbol> {
+ match *region {
+ ty::ReEarlyBound(r) => Some(r.name),
+ _ => None,
+ }
+}
+
+/// Replaces all [`ty::RegionVid`]s in a type with [`ty::Region`]s, using the provided map.
+struct RegionReplacer<'a, 'tcx> {
+ vid_to_region: &'a FxHashMap<ty::RegionVid, ty::Region<'tcx>>,
+ tcx: TyCtxt<'tcx>,
+}
+
+impl<'a, 'tcx> TypeFolder<'tcx> for RegionReplacer<'a, 'tcx> {
+ fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
+ self.tcx
+ }
+
+ fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
+ (match *r {
+ ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(),
+ _ => None,
+ })
+ .unwrap_or_else(|| r.super_fold_with(self))
+ }
+}
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
new file mode 100644
index 000000000..01dd95e6e
--- /dev/null
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -0,0 +1,136 @@
+use crate::rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
+use rustc_hir as hir;
+use rustc_infer::infer::{InferOk, TyCtxtInferExt};
+use rustc_infer::traits;
+use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::ToPredicate;
+use rustc_span::DUMMY_SP;
+
+use super::*;
+
+pub(crate) struct BlanketImplFinder<'a, 'tcx> {
+ pub(crate) cx: &'a mut core::DocContext<'tcx>,
+}
+
+impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
+ pub(crate) fn get_blanket_impls(&mut self, item_def_id: DefId) -> Vec<Item> {
+ let param_env = self.cx.tcx.param_env(item_def_id);
+ let ty = self.cx.tcx.bound_type_of(item_def_id);
+
+ trace!("get_blanket_impls({:?})", ty);
+ let mut impls = Vec::new();
+ self.cx.with_all_traits(|cx, all_traits| {
+ for &trait_def_id in all_traits {
+ if !cx.cache.access_levels.is_public(trait_def_id)
+ || cx.generated_synthetics.get(&(ty.0, trait_def_id)).is_some()
+ {
+ continue;
+ }
+ // NOTE: doesn't use `for_each_relevant_impl` to avoid looking at anything besides blanket impls
+ let trait_impls = cx.tcx.trait_impls_of(trait_def_id);
+ for &impl_def_id in trait_impls.blanket_impls() {
+ trace!(
+ "get_blanket_impls: Considering impl for trait '{:?}' {:?}",
+ trait_def_id,
+ impl_def_id
+ );
+ let trait_ref = cx.tcx.bound_impl_trait_ref(impl_def_id).unwrap();
+ let is_param = matches!(trait_ref.0.self_ty().kind(), ty::Param(_));
+ let may_apply = is_param && cx.tcx.infer_ctxt().enter(|infcx| {
+ let substs = infcx.fresh_substs_for_item(DUMMY_SP, item_def_id);
+ let ty = ty.subst(infcx.tcx, substs);
+ let param_env = EarlyBinder(param_env).subst(infcx.tcx, substs);
+
+ let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
+ let trait_ref = trait_ref.subst(infcx.tcx, impl_substs);
+
+ // Require the type the impl is implemented on to match
+ // our type, and ignore the impl if there was a mismatch.
+ let cause = traits::ObligationCause::dummy();
+ let eq_result = infcx.at(&cause, param_env).eq(trait_ref.self_ty(), ty);
+ if let Ok(InferOk { value: (), obligations }) = eq_result {
+ // FIXME(eddyb) ignoring `obligations` might cause false positives.
+ drop(obligations);
+
+ trace!(
+ "invoking predicate_may_hold: param_env={:?}, trait_ref={:?}, ty={:?}",
+ param_env,
+ trait_ref,
+ ty
+ );
+ let predicates = cx
+ .tcx
+ .predicates_of(impl_def_id)
+ .instantiate(cx.tcx, impl_substs)
+ .predicates
+ .into_iter()
+ .chain(Some(
+ ty::Binder::dummy(trait_ref)
+ .to_poly_trait_predicate()
+ .map_bound(ty::PredicateKind::Trait)
+ .to_predicate(infcx.tcx),
+ ));
+ for predicate in predicates {
+ debug!("testing predicate {:?}", predicate);
+ let obligation = traits::Obligation::new(
+ traits::ObligationCause::dummy(),
+ param_env,
+ predicate,
+ );
+ match infcx.evaluate_obligation(&obligation) {
+ Ok(eval_result) if eval_result.may_apply() => {}
+ Err(traits::OverflowError::Canonical) => {}
+ Err(traits::OverflowError::ErrorReporting) => {}
+ _ => {
+ return false;
+ }
+ }
+ }
+ true
+ } else {
+ false
+ }
+ });
+ debug!(
+ "get_blanket_impls: found applicable impl: {} for trait_ref={:?}, ty={:?}",
+ may_apply, trait_ref, ty
+ );
+ if !may_apply {
+ continue;
+ }
+
+ cx.generated_synthetics.insert((ty.0, trait_def_id));
+
+ impls.push(Item {
+ name: None,
+ attrs: Default::default(),
+ visibility: Inherited,
+ item_id: ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
+ kind: Box::new(ImplItem(Box::new(Impl {
+ unsafety: hir::Unsafety::Normal,
+ generics: clean_ty_generics(
+ cx,
+ cx.tcx.generics_of(impl_def_id),
+ cx.tcx.explicit_predicates_of(impl_def_id),
+ ),
+ // FIXME(eddyb) compute both `trait_` and `for_` from
+ // the post-inference `trait_ref`, as it's more accurate.
+ trait_: Some(clean_trait_ref_with_bindings(cx, trait_ref.0, &[])),
+ for_: clean_middle_ty(ty.0, cx, None),
+ items: cx.tcx
+ .associated_items(impl_def_id)
+ .in_definition_order()
+ .map(|x| x.clean(cx))
+ .collect::<Vec<_>>(),
+ polarity: ty::ImplPolarity::Positive,
+ kind: ImplKind::Blanket(Box::new(clean_middle_ty(trait_ref.0.self_ty(), cx, None))),
+ }))),
+ cfg: None,
+ });
+ }
+ }
+ });
+
+ impls
+ }
+}
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
new file mode 100644
index 000000000..f33f5d27d
--- /dev/null
+++ b/src/librustdoc/clean/cfg.rs
@@ -0,0 +1,588 @@
+//! The representation of a `#[doc(cfg(...))]` attribute.
+
+// FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
+// switch to use those structures instead.
+
+use std::fmt::{self, Write};
+use std::mem;
+use std::ops;
+
+use rustc_ast::{LitKind, MetaItem, MetaItemKind, NestedMetaItem};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_feature::Features;
+use rustc_session::parse::ParseSess;
+use rustc_span::symbol::{sym, Symbol};
+
+use rustc_span::Span;
+
+use crate::html::escape::Escape;
+
+#[cfg(test)]
+mod tests;
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) enum Cfg {
+ /// Accepts all configurations.
+ True,
+ /// Denies all configurations.
+ False,
+ /// A generic configuration option, e.g., `test` or `target_os = "linux"`.
+ Cfg(Symbol, Option<Symbol>),
+ /// Negates a configuration requirement, i.e., `not(x)`.
+ Not(Box<Cfg>),
+ /// Union of a list of configuration requirements, i.e., `any(...)`.
+ Any(Vec<Cfg>),
+ /// Intersection of a list of configuration requirements, i.e., `all(...)`.
+ All(Vec<Cfg>),
+}
+
+#[derive(PartialEq, Debug)]
+pub(crate) struct InvalidCfgError {
+ pub(crate) msg: &'static str,
+ pub(crate) span: Span,
+}
+
+impl Cfg {
+ /// Parses a `NestedMetaItem` into a `Cfg`.
+ fn parse_nested(
+ nested_cfg: &NestedMetaItem,
+ exclude: &FxHashSet<Cfg>,
+ ) -> Result<Option<Cfg>, InvalidCfgError> {
+ match nested_cfg {
+ NestedMetaItem::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude),
+ NestedMetaItem::Literal(ref lit) => {
+ Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
+ }
+ }
+ }
+
+ pub(crate) fn parse_without(
+ cfg: &MetaItem,
+ exclude: &FxHashSet<Cfg>,
+ ) -> Result<Option<Cfg>, InvalidCfgError> {
+ let name = match cfg.ident() {
+ Some(ident) => ident.name,
+ None => {
+ return Err(InvalidCfgError {
+ msg: "expected a single identifier",
+ span: cfg.span,
+ });
+ }
+ };
+ match cfg.kind {
+ MetaItemKind::Word => {
+ let cfg = Cfg::Cfg(name, None);
+ if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
+ }
+ MetaItemKind::NameValue(ref lit) => match lit.kind {
+ LitKind::Str(value, _) => {
+ let cfg = Cfg::Cfg(name, Some(value));
+ if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
+ }
+ _ => Err(InvalidCfgError {
+ // FIXME: if the main #[cfg] syntax decided to support non-string literals,
+ // this should be changed as well.
+ msg: "value of cfg option should be a string literal",
+ span: lit.span,
+ }),
+ },
+ MetaItemKind::List(ref items) => {
+ let orig_len = items.len();
+ let sub_cfgs =
+ items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
+ let ret = match name {
+ sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
+ sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
+ sym::not => {
+ if orig_len == 1 {
+ let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
+ if sub_cfgs.len() == 1 {
+ Ok(!sub_cfgs.pop().unwrap()?)
+ } else {
+ return Ok(None);
+ }
+ } else {
+ Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
+ }
+ }
+ _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
+ };
+ match ret {
+ Ok(c) => Ok(Some(c)),
+ Err(e) => Err(e),
+ }
+ }
+ }
+ }
+
+ /// Parses a `MetaItem` into a `Cfg`.
+ ///
+ /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
+ /// `target_os = "redox"`.
+ ///
+ /// If the content is not properly formatted, it will return an error indicating what and where
+ /// the error is.
+ pub(crate) fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
+ Self::parse_without(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
+ }
+
+ /// Checks whether the given configuration can be matched in the current session.
+ ///
+ /// Equivalent to `attr::cfg_matches`.
+ // FIXME: Actually make use of `features`.
+ pub(crate) fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
+ match *self {
+ Cfg::False => false,
+ Cfg::True => true,
+ Cfg::Not(ref child) => !child.matches(parse_sess, features),
+ Cfg::All(ref sub_cfgs) => {
+ sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
+ }
+ Cfg::Any(ref sub_cfgs) => {
+ sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
+ }
+ Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
+ }
+ }
+
+ /// Whether the configuration consists of just `Cfg` or `Not`.
+ fn is_simple(&self) -> bool {
+ match *self {
+ Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
+ Cfg::All(..) | Cfg::Any(..) => false,
+ }
+ }
+
+ /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
+ fn is_all(&self) -> bool {
+ match *self {
+ Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
+ Cfg::Any(..) => false,
+ }
+ }
+
+ /// Renders the configuration for human display, as a short HTML description.
+ pub(crate) fn render_short_html(&self) -> String {
+ let mut msg = Display(self, Format::ShortHtml).to_string();
+ if self.should_capitalize_first_letter() {
+ if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
+ msg[i..i + 1].make_ascii_uppercase();
+ }
+ }
+ msg
+ }
+
+ /// Renders the configuration for long display, as a long HTML description.
+ pub(crate) fn render_long_html(&self) -> String {
+ let on = if self.should_use_with_in_description() { "with" } else { "on" };
+
+ let mut msg =
+ format!("Available {on} <strong>{}</strong>", Display(self, Format::LongHtml));
+ if self.should_append_only_to_description() {
+ msg.push_str(" only");
+ }
+ msg.push('.');
+ msg
+ }
+
+ /// Renders the configuration for long display, as a long plain text description.
+ pub(crate) fn render_long_plain(&self) -> String {
+ let on = if self.should_use_with_in_description() { "with" } else { "on" };
+
+ let mut msg = format!("Available {on} {}", Display(self, Format::LongPlain));
+ if self.should_append_only_to_description() {
+ msg.push_str(" only");
+ }
+ msg
+ }
+
+ fn should_capitalize_first_letter(&self) -> bool {
+ match *self {
+ Cfg::False | Cfg::True | Cfg::Not(..) => true,
+ Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
+ sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
+ }
+ Cfg::Cfg(name, _) => name == sym::debug_assertions || name == sym::target_endian,
+ }
+ }
+
+ fn should_append_only_to_description(&self) -> bool {
+ match *self {
+ Cfg::False | Cfg::True => false,
+ Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
+ Cfg::Not(box Cfg::Cfg(..)) => true,
+ Cfg::Not(..) => false,
+ }
+ }
+
+ fn should_use_with_in_description(&self) -> bool {
+ matches!(self, Cfg::Cfg(sym::target_feature, _))
+ }
+
+ /// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will
+ /// return `None` if simplification managed to completely eliminate any requirements from this
+ /// `Cfg`.
+ ///
+ /// See `tests::test_simplify_with` for examples.
+ pub(crate) fn simplify_with(&self, assume: &Cfg) -> Option<Cfg> {
+ if self == assume {
+ return None;
+ }
+
+ if let Cfg::All(a) = self {
+ let mut sub_cfgs: Vec<Cfg> = if let Cfg::All(b) = assume {
+ a.iter().filter(|a| !b.contains(a)).cloned().collect()
+ } else {
+ a.iter().filter(|&a| a != assume).cloned().collect()
+ };
+ let len = sub_cfgs.len();
+ return match len {
+ 0 => None,
+ 1 => sub_cfgs.pop(),
+ _ => Some(Cfg::All(sub_cfgs)),
+ };
+ } else if let Cfg::All(b) = assume {
+ if b.contains(self) {
+ return None;
+ }
+ }
+
+ Some(self.clone())
+ }
+}
+
+impl ops::Not for Cfg {
+ type Output = Cfg;
+ fn not(self) -> Cfg {
+ match self {
+ Cfg::False => Cfg::True,
+ Cfg::True => Cfg::False,
+ Cfg::Not(cfg) => *cfg,
+ s => Cfg::Not(Box::new(s)),
+ }
+ }
+}
+
+impl ops::BitAndAssign for Cfg {
+ fn bitand_assign(&mut self, other: Cfg) {
+ match (self, other) {
+ (&mut Cfg::False, _) | (_, Cfg::True) => {}
+ (s, Cfg::False) => *s = Cfg::False,
+ (s @ &mut Cfg::True, b) => *s = b,
+ (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => {
+ for c in b.drain(..) {
+ if !a.contains(&c) {
+ a.push(c);
+ }
+ }
+ }
+ (&mut Cfg::All(ref mut a), ref mut b) => {
+ if !a.contains(b) {
+ a.push(mem::replace(b, Cfg::True));
+ }
+ }
+ (s, Cfg::All(mut a)) => {
+ let b = mem::replace(s, Cfg::True);
+ if !a.contains(&b) {
+ a.push(b);
+ }
+ *s = Cfg::All(a);
+ }
+ (s, b) => {
+ if *s != b {
+ let a = mem::replace(s, Cfg::True);
+ *s = Cfg::All(vec![a, b]);
+ }
+ }
+ }
+ }
+}
+
+impl ops::BitAnd for Cfg {
+ type Output = Cfg;
+ fn bitand(mut self, other: Cfg) -> Cfg {
+ self &= other;
+ self
+ }
+}
+
+impl ops::BitOrAssign for Cfg {
+ fn bitor_assign(&mut self, other: Cfg) {
+ match (self, other) {
+ (Cfg::True, _) | (_, Cfg::False) | (_, Cfg::True) => {}
+ (s @ &mut Cfg::False, b) => *s = b,
+ (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => {
+ for c in b.drain(..) {
+ if !a.contains(&c) {
+ a.push(c);
+ }
+ }
+ }
+ (&mut Cfg::Any(ref mut a), ref mut b) => {
+ if !a.contains(b) {
+ a.push(mem::replace(b, Cfg::True));
+ }
+ }
+ (s, Cfg::Any(mut a)) => {
+ let b = mem::replace(s, Cfg::True);
+ if !a.contains(&b) {
+ a.push(b);
+ }
+ *s = Cfg::Any(a);
+ }
+ (s, b) => {
+ if *s != b {
+ let a = mem::replace(s, Cfg::True);
+ *s = Cfg::Any(vec![a, b]);
+ }
+ }
+ }
+ }
+}
+
+impl ops::BitOr for Cfg {
+ type Output = Cfg;
+ fn bitor(mut self, other: Cfg) -> Cfg {
+ self |= other;
+ self
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Format {
+ LongHtml,
+ LongPlain,
+ ShortHtml,
+}
+
+impl Format {
+ fn is_long(self) -> bool {
+ match self {
+ Format::LongHtml | Format::LongPlain => true,
+ Format::ShortHtml => false,
+ }
+ }
+
+ fn is_html(self) -> bool {
+ match self {
+ Format::LongHtml | Format::ShortHtml => true,
+ Format::LongPlain => false,
+ }
+ }
+}
+
+/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
+struct Display<'a>(&'a Cfg, Format);
+
+fn write_with_opt_paren<T: fmt::Display>(
+ fmt: &mut fmt::Formatter<'_>,
+ has_paren: bool,
+ obj: T,
+) -> fmt::Result {
+ if has_paren {
+ fmt.write_char('(')?;
+ }
+ obj.fmt(fmt)?;
+ if has_paren {
+ fmt.write_char(')')?;
+ }
+ Ok(())
+}
+
+impl<'a> fmt::Display for Display<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self.0 {
+ Cfg::Not(ref child) => match **child {
+ Cfg::Any(ref sub_cfgs) => {
+ let separator =
+ if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
+ for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+ fmt.write_str(if i == 0 { "neither " } else { separator })?;
+ write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
+ }
+ Ok(())
+ }
+ ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
+ ref c => write!(fmt, "not ({})", Display(c, self.1)),
+ },
+
+ Cfg::Any(ref sub_cfgs) => {
+ let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
+
+ let short_longhand = self.1.is_long() && {
+ let all_crate_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
+ let all_target_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
+
+ if all_crate_features {
+ fmt.write_str("crate features ")?;
+ true
+ } else if all_target_features {
+ fmt.write_str("target features ")?;
+ true
+ } else {
+ false
+ }
+ };
+
+ for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+ if i != 0 {
+ fmt.write_str(separator)?;
+ }
+ if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
+ if self.1.is_html() {
+ write!(fmt, "<code>{}</code>", feat)?;
+ } else {
+ write!(fmt, "`{}`", feat)?;
+ }
+ } else {
+ write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
+ }
+ }
+ Ok(())
+ }
+
+ Cfg::All(ref sub_cfgs) => {
+ let short_longhand = self.1.is_long() && {
+ let all_crate_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
+ let all_target_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
+
+ if all_crate_features {
+ fmt.write_str("crate features ")?;
+ true
+ } else if all_target_features {
+ fmt.write_str("target features ")?;
+ true
+ } else {
+ false
+ }
+ };
+
+ for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+ if i != 0 {
+ fmt.write_str(" and ")?;
+ }
+ if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
+ if self.1.is_html() {
+ write!(fmt, "<code>{}</code>", feat)?;
+ } else {
+ write!(fmt, "`{}`", feat)?;
+ }
+ } else {
+ write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?;
+ }
+ }
+ Ok(())
+ }
+
+ Cfg::True => fmt.write_str("everywhere"),
+ Cfg::False => fmt.write_str("nowhere"),
+
+ Cfg::Cfg(name, value) => {
+ let human_readable = match (name, value) {
+ (sym::unix, None) => "Unix",
+ (sym::windows, None) => "Windows",
+ (sym::debug_assertions, None) => "debug-assertions enabled",
+ (sym::target_os, Some(os)) => match os.as_str() {
+ "android" => "Android",
+ "dragonfly" => "DragonFly BSD",
+ "emscripten" => "Emscripten",
+ "freebsd" => "FreeBSD",
+ "fuchsia" => "Fuchsia",
+ "haiku" => "Haiku",
+ "hermit" => "HermitCore",
+ "illumos" => "illumos",
+ "ios" => "iOS",
+ "l4re" => "L4Re",
+ "linux" => "Linux",
+ "macos" => "macOS",
+ "netbsd" => "NetBSD",
+ "openbsd" => "OpenBSD",
+ "redox" => "Redox",
+ "solaris" => "Solaris",
+ "wasi" => "WASI",
+ "windows" => "Windows",
+ _ => "",
+ },
+ (sym::target_arch, Some(arch)) => match arch.as_str() {
+ "aarch64" => "AArch64",
+ "arm" => "ARM",
+ "asmjs" => "JavaScript",
+ "m68k" => "M68k",
+ "mips" => "MIPS",
+ "mips64" => "MIPS-64",
+ "msp430" => "MSP430",
+ "powerpc" => "PowerPC",
+ "powerpc64" => "PowerPC-64",
+ "riscv32" => "RISC-V RV32",
+ "riscv64" => "RISC-V RV64",
+ "s390x" => "s390x",
+ "sparc64" => "SPARC64",
+ "wasm32" | "wasm64" => "WebAssembly",
+ "x86" => "x86",
+ "x86_64" => "x86-64",
+ _ => "",
+ },
+ (sym::target_vendor, Some(vendor)) => match vendor.as_str() {
+ "apple" => "Apple",
+ "pc" => "PC",
+ "sun" => "Sun",
+ "fortanix" => "Fortanix",
+ _ => "",
+ },
+ (sym::target_env, Some(env)) => match env.as_str() {
+ "gnu" => "GNU",
+ "msvc" => "MSVC",
+ "musl" => "musl",
+ "newlib" => "Newlib",
+ "uclibc" => "uClibc",
+ "sgx" => "SGX",
+ _ => "",
+ },
+ (sym::target_endian, Some(endian)) => return write!(fmt, "{}-endian", endian),
+ (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{}-bit", bits),
+ (sym::target_feature, Some(feat)) => match self.1 {
+ Format::LongHtml => {
+ return write!(fmt, "target feature <code>{}</code>", feat);
+ }
+ Format::LongPlain => return write!(fmt, "target feature `{}`", feat),
+ Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
+ },
+ (sym::feature, Some(feat)) => match self.1 {
+ Format::LongHtml => {
+ return write!(fmt, "crate feature <code>{}</code>", feat);
+ }
+ Format::LongPlain => return write!(fmt, "crate feature `{}`", feat),
+ Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
+ },
+ _ => "",
+ };
+ if !human_readable.is_empty() {
+ fmt.write_str(human_readable)
+ } else if let Some(v) = value {
+ if self.1.is_html() {
+ write!(
+ fmt,
+ r#"<code>{}="{}"</code>"#,
+ Escape(name.as_str()),
+ Escape(v.as_str())
+ )
+ } else {
+ write!(fmt, r#"`{}="{}"`"#, name, v)
+ }
+ } else if self.1.is_html() {
+ write!(fmt, "<code>{}</code>", Escape(name.as_str()))
+ } else {
+ write!(fmt, "`{}`", name)
+ }
+ }
+ }
+ }
+}
diff --git a/src/librustdoc/clean/cfg/tests.rs b/src/librustdoc/clean/cfg/tests.rs
new file mode 100644
index 000000000..7f72d5d39
--- /dev/null
+++ b/src/librustdoc/clean/cfg/tests.rs
@@ -0,0 +1,467 @@
+use super::*;
+
+use rustc_ast::attr;
+use rustc_ast::Path;
+use rustc_span::create_default_session_globals_then;
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::DUMMY_SP;
+
+fn word_cfg(s: &str) -> Cfg {
+ Cfg::Cfg(Symbol::intern(s), None)
+}
+
+fn name_value_cfg(name: &str, value: &str) -> Cfg {
+ Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value)))
+}
+
+fn dummy_meta_item_word(name: &str) -> MetaItem {
+ MetaItem {
+ path: Path::from_ident(Ident::from_str(name)),
+ kind: MetaItemKind::Word,
+ span: DUMMY_SP,
+ }
+}
+
+macro_rules! dummy_meta_item_list {
+ ($name:ident, [$($list:ident),* $(,)?]) => {
+ MetaItem {
+ path: Path::from_ident(Ident::from_str(stringify!($name))),
+ kind: MetaItemKind::List(vec![
+ $(
+ NestedMetaItem::MetaItem(
+ dummy_meta_item_word(stringify!($list)),
+ ),
+ )*
+ ]),
+ span: DUMMY_SP,
+ }
+ };
+
+ ($name:ident, [$($list:expr),* $(,)?]) => {
+ MetaItem {
+ path: Path::from_ident(Ident::from_str(stringify!($name))),
+ kind: MetaItemKind::List(vec![
+ $(
+ NestedMetaItem::MetaItem($list),
+ )*
+ ]),
+ span: DUMMY_SP,
+ }
+ };
+}
+
+#[test]
+fn test_cfg_not() {
+ create_default_session_globals_then(|| {
+ assert_eq!(!Cfg::False, Cfg::True);
+ assert_eq!(!Cfg::True, Cfg::False);
+ assert_eq!(!word_cfg("test"), Cfg::Not(Box::new(word_cfg("test"))));
+ assert_eq!(
+ !Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
+ Cfg::Not(Box::new(Cfg::All(vec![word_cfg("a"), word_cfg("b")])))
+ );
+ assert_eq!(
+ !Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
+ Cfg::Not(Box::new(Cfg::Any(vec![word_cfg("a"), word_cfg("b")])))
+ );
+ assert_eq!(!Cfg::Not(Box::new(word_cfg("test"))), word_cfg("test"));
+ })
+}
+
+#[test]
+fn test_cfg_and() {
+ create_default_session_globals_then(|| {
+ let mut x = Cfg::False;
+ x &= Cfg::True;
+ assert_eq!(x, Cfg::False);
+
+ x = word_cfg("test");
+ x &= Cfg::False;
+ assert_eq!(x, Cfg::False);
+
+ x = word_cfg("test2");
+ x &= Cfg::True;
+ assert_eq!(x, word_cfg("test2"));
+
+ x = Cfg::True;
+ x &= word_cfg("test3");
+ assert_eq!(x, word_cfg("test3"));
+
+ x &= word_cfg("test3");
+ assert_eq!(x, word_cfg("test3"));
+
+ x &= word_cfg("test4");
+ assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
+
+ x &= word_cfg("test4");
+ assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
+
+ x &= word_cfg("test5");
+ assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
+
+ x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
+ assert_eq!(
+ x,
+ Cfg::All(vec![
+ word_cfg("test3"),
+ word_cfg("test4"),
+ word_cfg("test5"),
+ word_cfg("test6"),
+ word_cfg("test7"),
+ ])
+ );
+
+ x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
+ assert_eq!(
+ x,
+ Cfg::All(vec![
+ word_cfg("test3"),
+ word_cfg("test4"),
+ word_cfg("test5"),
+ word_cfg("test6"),
+ word_cfg("test7"),
+ ])
+ );
+
+ let mut y = Cfg::Any(vec![word_cfg("a"), word_cfg("b")]);
+ y &= x;
+ assert_eq!(
+ y,
+ Cfg::All(vec![
+ word_cfg("test3"),
+ word_cfg("test4"),
+ word_cfg("test5"),
+ word_cfg("test6"),
+ word_cfg("test7"),
+ Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
+ ])
+ );
+
+ let mut z = word_cfg("test8");
+ z &= Cfg::All(vec![word_cfg("test9"), word_cfg("test10")]);
+ assert_eq!(z, Cfg::All(vec![word_cfg("test9"), word_cfg("test10"), word_cfg("test8")]));
+
+ let mut z = word_cfg("test11");
+ z &= Cfg::All(vec![word_cfg("test11"), word_cfg("test12")]);
+ assert_eq!(z, Cfg::All(vec![word_cfg("test11"), word_cfg("test12")]));
+
+ assert_eq!(
+ word_cfg("a") & word_cfg("b") & word_cfg("c"),
+ Cfg::All(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
+ );
+ })
+}
+
+#[test]
+fn test_cfg_or() {
+ create_default_session_globals_then(|| {
+ let mut x = Cfg::True;
+ x |= Cfg::False;
+ assert_eq!(x, Cfg::True);
+
+ x = word_cfg("test");
+ x |= Cfg::True;
+ assert_eq!(x, word_cfg("test"));
+
+ x = word_cfg("test2");
+ x |= Cfg::False;
+ assert_eq!(x, word_cfg("test2"));
+
+ x = Cfg::False;
+ x |= word_cfg("test3");
+ assert_eq!(x, word_cfg("test3"));
+
+ x |= word_cfg("test3");
+ assert_eq!(x, word_cfg("test3"));
+
+ x |= word_cfg("test4");
+ assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
+
+ x |= word_cfg("test4");
+ assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
+
+ x |= word_cfg("test5");
+ assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
+
+ x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
+ assert_eq!(
+ x,
+ Cfg::Any(vec![
+ word_cfg("test3"),
+ word_cfg("test4"),
+ word_cfg("test5"),
+ word_cfg("test6"),
+ word_cfg("test7"),
+ ])
+ );
+
+ x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
+ assert_eq!(
+ x,
+ Cfg::Any(vec![
+ word_cfg("test3"),
+ word_cfg("test4"),
+ word_cfg("test5"),
+ word_cfg("test6"),
+ word_cfg("test7"),
+ ])
+ );
+
+ let mut y = Cfg::All(vec![word_cfg("a"), word_cfg("b")]);
+ y |= x;
+ assert_eq!(
+ y,
+ Cfg::Any(vec![
+ word_cfg("test3"),
+ word_cfg("test4"),
+ word_cfg("test5"),
+ word_cfg("test6"),
+ word_cfg("test7"),
+ Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
+ ])
+ );
+
+ let mut z = word_cfg("test8");
+ z |= Cfg::Any(vec![word_cfg("test9"), word_cfg("test10")]);
+ assert_eq!(z, Cfg::Any(vec![word_cfg("test9"), word_cfg("test10"), word_cfg("test8")]));
+
+ let mut z = word_cfg("test11");
+ z |= Cfg::Any(vec![word_cfg("test11"), word_cfg("test12")]);
+ assert_eq!(z, Cfg::Any(vec![word_cfg("test11"), word_cfg("test12")]));
+
+ assert_eq!(
+ word_cfg("a") | word_cfg("b") | word_cfg("c"),
+ Cfg::Any(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
+ );
+ })
+}
+
+#[test]
+fn test_parse_ok() {
+ create_default_session_globals_then(|| {
+ let mi = dummy_meta_item_word("all");
+ assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
+
+ let mi =
+ attr::mk_name_value_item_str(Ident::from_str("all"), Symbol::intern("done"), DUMMY_SP);
+ assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
+
+ let mi = dummy_meta_item_list!(all, [a, b]);
+ assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
+
+ let mi = dummy_meta_item_list!(any, [a, b]);
+ assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
+
+ let mi = dummy_meta_item_list!(not, [a]);
+ assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
+
+ let mi = dummy_meta_item_list!(
+ not,
+ [dummy_meta_item_list!(
+ any,
+ [dummy_meta_item_word("a"), dummy_meta_item_list!(all, [b, c]),]
+ ),]
+ );
+ assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
+
+ let mi = dummy_meta_item_list!(all, [a, b, c]);
+ assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
+ })
+}
+
+#[test]
+fn test_parse_err() {
+ create_default_session_globals_then(|| {
+ let mi = attr::mk_name_value_item(Ident::from_str("foo"), LitKind::Bool(false), DUMMY_SP);
+ assert!(Cfg::parse(&mi).is_err());
+
+ let mi = dummy_meta_item_list!(not, [a, b]);
+ assert!(Cfg::parse(&mi).is_err());
+
+ let mi = dummy_meta_item_list!(not, []);
+ assert!(Cfg::parse(&mi).is_err());
+
+ let mi = dummy_meta_item_list!(foo, []);
+ assert!(Cfg::parse(&mi).is_err());
+
+ let mi = dummy_meta_item_list!(
+ all,
+ [dummy_meta_item_list!(foo, []), dummy_meta_item_word("b"),]
+ );
+ assert!(Cfg::parse(&mi).is_err());
+
+ let mi = dummy_meta_item_list!(
+ any,
+ [dummy_meta_item_word("a"), dummy_meta_item_list!(foo, []),]
+ );
+ assert!(Cfg::parse(&mi).is_err());
+
+ let mi = dummy_meta_item_list!(not, [dummy_meta_item_list!(foo, []),]);
+ assert!(Cfg::parse(&mi).is_err());
+ })
+}
+
+#[test]
+fn test_render_short_html() {
+ create_default_session_globals_then(|| {
+ assert_eq!(word_cfg("unix").render_short_html(), "Unix");
+ assert_eq!(name_value_cfg("target_os", "macos").render_short_html(), "macOS");
+ assert_eq!(name_value_cfg("target_pointer_width", "16").render_short_html(), "16-bit");
+ assert_eq!(name_value_cfg("target_endian", "little").render_short_html(), "Little-endian");
+ assert_eq!((!word_cfg("windows")).render_short_html(), "Non-Windows");
+ assert_eq!(
+ (word_cfg("unix") & word_cfg("windows")).render_short_html(),
+ "Unix and Windows"
+ );
+ assert_eq!((word_cfg("unix") | word_cfg("windows")).render_short_html(), "Unix or Windows");
+ assert_eq!(
+ (word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions"))
+ .render_short_html(),
+ "Unix and Windows and debug-assertions enabled"
+ );
+ assert_eq!(
+ (word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
+ .render_short_html(),
+ "Unix or Windows or debug-assertions enabled"
+ );
+ assert_eq!(
+ (!(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")))
+ .render_short_html(),
+ "Neither Unix nor Windows nor debug-assertions enabled"
+ );
+ assert_eq!(
+ ((word_cfg("unix") & name_value_cfg("target_arch", "x86_64"))
+ | (word_cfg("windows") & name_value_cfg("target_pointer_width", "64")))
+ .render_short_html(),
+ "Unix and x86-64, or Windows and 64-bit"
+ );
+ assert_eq!(
+ (!(word_cfg("unix") & word_cfg("windows"))).render_short_html(),
+ "Not (Unix and Windows)"
+ );
+ assert_eq!(
+ ((word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix"))
+ .render_short_html(),
+ "(Debug-assertions enabled or Windows) and Unix"
+ );
+ assert_eq!(
+ name_value_cfg("target_feature", "sse2").render_short_html(),
+ "<code>sse2</code>"
+ );
+ assert_eq!(
+ (name_value_cfg("target_arch", "x86_64") & name_value_cfg("target_feature", "sse2"))
+ .render_short_html(),
+ "x86-64 and <code>sse2</code>"
+ );
+ })
+}
+
+#[test]
+fn test_render_long_html() {
+ create_default_session_globals_then(|| {
+ assert_eq!(word_cfg("unix").render_long_html(), "Available on <strong>Unix</strong> only.");
+ assert_eq!(
+ name_value_cfg("target_os", "macos").render_long_html(),
+ "Available on <strong>macOS</strong> only."
+ );
+ assert_eq!(
+ name_value_cfg("target_os", "wasi").render_long_html(),
+ "Available on <strong>WASI</strong> only."
+ );
+ assert_eq!(
+ name_value_cfg("target_pointer_width", "16").render_long_html(),
+ "Available on <strong>16-bit</strong> only."
+ );
+ assert_eq!(
+ name_value_cfg("target_endian", "little").render_long_html(),
+ "Available on <strong>little-endian</strong> only."
+ );
+ assert_eq!(
+ (!word_cfg("windows")).render_long_html(),
+ "Available on <strong>non-Windows</strong> only."
+ );
+ assert_eq!(
+ (word_cfg("unix") & word_cfg("windows")).render_long_html(),
+ "Available on <strong>Unix and Windows</strong> only."
+ );
+ assert_eq!(
+ (word_cfg("unix") | word_cfg("windows")).render_long_html(),
+ "Available on <strong>Unix or Windows</strong> only."
+ );
+ assert_eq!(
+ (word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions"))
+ .render_long_html(),
+ "Available on <strong>Unix and Windows and debug-assertions enabled</strong> only."
+ );
+ assert_eq!(
+ (word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
+ .render_long_html(),
+ "Available on <strong>Unix or Windows or debug-assertions enabled</strong> only."
+ );
+ assert_eq!(
+ (!(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")))
+ .render_long_html(),
+ "Available on <strong>neither Unix nor Windows nor debug-assertions enabled</strong>."
+ );
+ assert_eq!(
+ ((word_cfg("unix") & name_value_cfg("target_arch", "x86_64"))
+ | (word_cfg("windows") & name_value_cfg("target_pointer_width", "64")))
+ .render_long_html(),
+ "Available on <strong>Unix and x86-64, or Windows and 64-bit</strong> only."
+ );
+ assert_eq!(
+ (!(word_cfg("unix") & word_cfg("windows"))).render_long_html(),
+ "Available on <strong>not (Unix and Windows)</strong>."
+ );
+ assert_eq!(
+ ((word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix"))
+ .render_long_html(),
+ "Available on <strong>(debug-assertions enabled or Windows) and Unix</strong> only."
+ );
+ assert_eq!(
+ name_value_cfg("target_feature", "sse2").render_long_html(),
+ "Available with <strong>target feature <code>sse2</code></strong> only."
+ );
+ assert_eq!(
+ (name_value_cfg("target_arch", "x86_64") & name_value_cfg("target_feature", "sse2"))
+ .render_long_html(),
+ "Available on <strong>x86-64 and target feature <code>sse2</code></strong> only."
+ );
+ })
+}
+
+#[test]
+fn test_simplify_with() {
+ // This is a tiny subset of things that could be simplified, but it likely covers 90% of
+ // real world usecases well.
+ create_default_session_globals_then(|| {
+ let foo = word_cfg("foo");
+ let bar = word_cfg("bar");
+ let baz = word_cfg("baz");
+ let quux = word_cfg("quux");
+
+ let foobar = Cfg::All(vec![foo.clone(), bar.clone()]);
+ let barbaz = Cfg::All(vec![bar.clone(), baz.clone()]);
+ let foobarbaz = Cfg::All(vec![foo.clone(), bar.clone(), baz.clone()]);
+ let bazquux = Cfg::All(vec![baz.clone(), quux.clone()]);
+
+ // Unrelated cfgs don't affect each other
+ assert_eq!(foo.simplify_with(&bar).as_ref(), Some(&foo));
+ assert_eq!(foobar.simplify_with(&bazquux).as_ref(), Some(&foobar));
+
+ // Identical cfgs are eliminated
+ assert_eq!(foo.simplify_with(&foo), None);
+ assert_eq!(foobar.simplify_with(&foobar), None);
+
+ // Multiple cfgs eliminate a single assumed cfg
+ assert_eq!(foobar.simplify_with(&foo).as_ref(), Some(&bar));
+ assert_eq!(foobar.simplify_with(&bar).as_ref(), Some(&foo));
+
+ // A single cfg is eliminated by multiple assumed cfg containing it
+ assert_eq!(foo.simplify_with(&foobar), None);
+
+ // Multiple cfgs eliminate the matching subset of multiple assumed cfg
+ assert_eq!(foobar.simplify_with(&barbaz).as_ref(), Some(&foo));
+ assert_eq!(foobar.simplify_with(&foobarbaz), None);
+ });
+}
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
new file mode 100644
index 000000000..58d0aedb0
--- /dev/null
+++ b/src/librustdoc/clean/inline.rs
@@ -0,0 +1,727 @@
+//! Support for inlining external documentation into the current AST.
+
+use std::iter::once;
+use std::sync::Arc;
+
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::thin_vec::ThinVec;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::Mutability;
+use rustc_metadata::creader::{CStore, LoadedMacro};
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_span::hygiene::MacroKind;
+use rustc_span::symbol::{kw, sym, Symbol};
+
+use crate::clean::{
+ self, clean_fn_decl_from_did_and_sig, clean_middle_field, clean_middle_ty,
+ clean_trait_ref_with_bindings, clean_ty, clean_ty_generics, clean_variant_def,
+ clean_visibility, utils, Attributes, AttributesExt, Clean, ImplKind, ItemId, Type, Visibility,
+};
+use crate::core::DocContext;
+use crate::formats::item_type::ItemType;
+
+type Attrs<'hir> = &'hir [ast::Attribute];
+
+/// Attempt to inline a definition into this AST.
+///
+/// This function will fetch the definition specified, and if it is
+/// from another crate it will attempt to inline the documentation
+/// from the other crate into this crate.
+///
+/// This is primarily used for `pub use` statements which are, in general,
+/// implementation details. Inlining the documentation should help provide a
+/// better experience when reading the documentation in this use case.
+///
+/// The returned value is `None` if the definition could not be inlined,
+/// and `Some` of a vector of items if it was successfully expanded.
+///
+/// `parent_module` refers to the parent of the *re-export*, not the original item.
+pub(crate) fn try_inline(
+ cx: &mut DocContext<'_>,
+ parent_module: DefId,
+ import_def_id: Option<DefId>,
+ res: Res,
+ name: Symbol,
+ attrs: Option<Attrs<'_>>,
+ visited: &mut FxHashSet<DefId>,
+) -> Option<Vec<clean::Item>> {
+ let did = res.opt_def_id()?;
+ if did.is_local() {
+ return None;
+ }
+ let mut ret = Vec::new();
+
+ debug!("attrs={:?}", attrs);
+ let attrs_clone = attrs;
+
+ let kind = match res {
+ Res::Def(DefKind::Trait, did) => {
+ record_extern_fqn(cx, did, ItemType::Trait);
+ build_impls(cx, Some(parent_module), did, attrs, &mut ret);
+ clean::TraitItem(build_external_trait(cx, did))
+ }
+ Res::Def(DefKind::Fn, did) => {
+ record_extern_fqn(cx, did, ItemType::Function);
+ clean::FunctionItem(build_external_function(cx, did))
+ }
+ Res::Def(DefKind::Struct, did) => {
+ record_extern_fqn(cx, did, ItemType::Struct);
+ build_impls(cx, Some(parent_module), did, attrs, &mut ret);
+ clean::StructItem(build_struct(cx, did))
+ }
+ Res::Def(DefKind::Union, did) => {
+ record_extern_fqn(cx, did, ItemType::Union);
+ build_impls(cx, Some(parent_module), did, attrs, &mut ret);
+ clean::UnionItem(build_union(cx, did))
+ }
+ Res::Def(DefKind::TyAlias, did) => {
+ record_extern_fqn(cx, did, ItemType::Typedef);
+ build_impls(cx, Some(parent_module), did, attrs, &mut ret);
+ clean::TypedefItem(build_type_alias(cx, did))
+ }
+ Res::Def(DefKind::Enum, did) => {
+ record_extern_fqn(cx, did, ItemType::Enum);
+ build_impls(cx, Some(parent_module), did, attrs, &mut ret);
+ clean::EnumItem(build_enum(cx, did))
+ }
+ Res::Def(DefKind::ForeignTy, did) => {
+ record_extern_fqn(cx, did, ItemType::ForeignType);
+ build_impls(cx, Some(parent_module), did, attrs, &mut ret);
+ clean::ForeignTypeItem
+ }
+ // Never inline enum variants but leave them shown as re-exports.
+ Res::Def(DefKind::Variant, _) => return None,
+ // Assume that enum variants and struct types are re-exported next to
+ // their constructors.
+ Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => return Some(Vec::new()),
+ Res::Def(DefKind::Mod, did) => {
+ record_extern_fqn(cx, did, ItemType::Module);
+ clean::ModuleItem(build_module(cx, did, visited))
+ }
+ Res::Def(DefKind::Static(_), did) => {
+ record_extern_fqn(cx, did, ItemType::Static);
+ clean::StaticItem(build_static(cx, did, cx.tcx.is_mutable_static(did)))
+ }
+ Res::Def(DefKind::Const, did) => {
+ record_extern_fqn(cx, did, ItemType::Constant);
+ clean::ConstantItem(build_const(cx, did))
+ }
+ Res::Def(DefKind::Macro(kind), did) => {
+ let mac = build_macro(cx, did, name, import_def_id);
+
+ let type_kind = match kind {
+ MacroKind::Bang => ItemType::Macro,
+ MacroKind::Attr => ItemType::ProcAttribute,
+ MacroKind::Derive => ItemType::ProcDerive,
+ };
+ record_extern_fqn(cx, did, type_kind);
+ mac
+ }
+ _ => return None,
+ };
+
+ let (attrs, cfg) = merge_attrs(cx, Some(parent_module), load_attrs(cx, did), attrs_clone);
+ cx.inlined.insert(did.into());
+ let mut item = clean::Item::from_def_id_and_attrs_and_parts(
+ did,
+ Some(name),
+ kind,
+ Box::new(attrs),
+ cx,
+ cfg,
+ );
+ if let Some(import_def_id) = import_def_id {
+ // The visibility needs to reflect the one from the reexport and not from the "source" DefId.
+ item.visibility = clean_visibility(cx.tcx.visibility(import_def_id));
+ }
+ ret.push(item);
+ Some(ret)
+}
+
+pub(crate) fn try_inline_glob(
+ cx: &mut DocContext<'_>,
+ res: Res,
+ visited: &mut FxHashSet<DefId>,
+ inlined_names: &mut FxHashSet<(ItemType, Symbol)>,
+) -> Option<Vec<clean::Item>> {
+ let did = res.opt_def_id()?;
+ if did.is_local() {
+ return None;
+ }
+
+ match res {
+ Res::Def(DefKind::Mod, did) => {
+ let mut items = build_module_items(cx, did, visited, inlined_names);
+ items.drain_filter(|item| {
+ if let Some(name) = item.name {
+ // If an item with the same type and name already exists,
+ // it takes priority over the inlined stuff.
+ !inlined_names.insert((item.type_(), name))
+ } else {
+ false
+ }
+ });
+ Some(items)
+ }
+ // glob imports on things like enums aren't inlined even for local exports, so just bail
+ _ => None,
+ }
+}
+
+pub(crate) fn load_attrs<'hir>(cx: &DocContext<'hir>, did: DefId) -> Attrs<'hir> {
+ cx.tcx.get_attrs_unchecked(did)
+}
+
+/// Record an external fully qualified name in the external_paths cache.
+///
+/// These names are used later on by HTML rendering to generate things like
+/// source links back to the original item.
+pub(crate) fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemType) {
+ let crate_name = cx.tcx.crate_name(did.krate);
+
+ let relative =
+ cx.tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
+ let fqn = if let ItemType::Macro = kind {
+ // Check to see if it is a macro 2.0 or built-in macro
+ if matches!(
+ CStore::from_tcx(cx.tcx).load_macro_untracked(did, cx.sess()),
+ LoadedMacro::MacroDef(def, _)
+ if matches!(&def.kind, ast::ItemKind::MacroDef(ast_def)
+ if !ast_def.macro_rules)
+ ) {
+ once(crate_name).chain(relative).collect()
+ } else {
+ vec![crate_name, relative.last().expect("relative was empty")]
+ }
+ } else {
+ once(crate_name).chain(relative).collect()
+ };
+
+ if did.is_local() {
+ cx.cache.exact_paths.insert(did, fqn);
+ } else {
+ cx.cache.external_paths.insert(did, (fqn, kind));
+ }
+}
+
+pub(crate) fn build_external_trait(cx: &mut DocContext<'_>, did: DefId) -> clean::Trait {
+ let trait_items = cx
+ .tcx
+ .associated_items(did)
+ .in_definition_order()
+ .map(|item| {
+ // When building an external trait, the cleaned trait will have all items public,
+ // which causes methods to have a `pub` prefix, which is invalid since items in traits
+ // can not have a visibility prefix. Thus we override the visibility here manually.
+ // See https://github.com/rust-lang/rust/issues/81274
+ clean::Item { visibility: Visibility::Inherited, ..item.clean(cx) }
+ })
+ .collect();
+
+ let predicates = cx.tcx.predicates_of(did);
+ let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates);
+ let generics = filter_non_trait_generics(did, generics);
+ let (generics, supertrait_bounds) = separate_supertrait_bounds(generics);
+ clean::Trait { def_id: did, generics, items: trait_items, bounds: supertrait_bounds }
+}
+
+fn build_external_function<'tcx>(cx: &mut DocContext<'tcx>, did: DefId) -> Box<clean::Function> {
+ let sig = cx.tcx.fn_sig(did);
+
+ let predicates = cx.tcx.predicates_of(did);
+ let (generics, decl) = clean::enter_impl_trait(cx, |cx| {
+ // NOTE: generics need to be cleaned before the decl!
+ let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates);
+ let decl = clean_fn_decl_from_did_and_sig(cx, Some(did), sig);
+ (generics, decl)
+ });
+ Box::new(clean::Function { decl, generics })
+}
+
+fn build_enum(cx: &mut DocContext<'_>, did: DefId) -> clean::Enum {
+ let predicates = cx.tcx.explicit_predicates_of(did);
+
+ clean::Enum {
+ generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates),
+ variants: cx.tcx.adt_def(did).variants().iter().map(|v| clean_variant_def(v, cx)).collect(),
+ }
+}
+
+fn build_struct(cx: &mut DocContext<'_>, did: DefId) -> clean::Struct {
+ let predicates = cx.tcx.explicit_predicates_of(did);
+ let variant = cx.tcx.adt_def(did).non_enum_variant();
+
+ clean::Struct {
+ struct_type: variant.ctor_kind,
+ generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates),
+ fields: variant.fields.iter().map(|x| clean_middle_field(x, cx)).collect(),
+ }
+}
+
+fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union {
+ let predicates = cx.tcx.explicit_predicates_of(did);
+ let variant = cx.tcx.adt_def(did).non_enum_variant();
+
+ let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates);
+ let fields = variant.fields.iter().map(|x| clean_middle_field(x, cx)).collect();
+ clean::Union { generics, fields }
+}
+
+fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::Typedef> {
+ let predicates = cx.tcx.explicit_predicates_of(did);
+ let type_ = clean_middle_ty(cx.tcx.type_of(did), cx, Some(did));
+
+ Box::new(clean::Typedef {
+ type_,
+ generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates),
+ item_type: None,
+ })
+}
+
+/// Builds all inherent implementations of an ADT (struct/union/enum) or Trait item/path/reexport.
+pub(crate) fn build_impls(
+ cx: &mut DocContext<'_>,
+ parent_module: Option<DefId>,
+ did: DefId,
+ attrs: Option<Attrs<'_>>,
+ ret: &mut Vec<clean::Item>,
+) {
+ let _prof_timer = cx.tcx.sess.prof.generic_activity("build_inherent_impls");
+ let tcx = cx.tcx;
+
+ // for each implementation of an item represented by `did`, build the clean::Item for that impl
+ for &did in tcx.inherent_impls(did).iter() {
+ build_impl(cx, parent_module, did, attrs, ret);
+ }
+}
+
+/// `parent_module` refers to the parent of the re-export, not the original item
+fn merge_attrs(
+ cx: &mut DocContext<'_>,
+ parent_module: Option<DefId>,
+ old_attrs: Attrs<'_>,
+ new_attrs: Option<Attrs<'_>>,
+) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
+ // NOTE: If we have additional attributes (from a re-export),
+ // always insert them first. This ensure that re-export
+ // doc comments show up before the original doc comments
+ // when we render them.
+ if let Some(inner) = new_attrs {
+ let mut both = inner.to_vec();
+ both.extend_from_slice(old_attrs);
+ (
+ if let Some(new_id) = parent_module {
+ Attributes::from_ast_with_additional(old_attrs, (inner, new_id))
+ } else {
+ Attributes::from_ast(&both)
+ },
+ both.cfg(cx.tcx, &cx.cache.hidden_cfg),
+ )
+ } else {
+ (Attributes::from_ast(&old_attrs), old_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg))
+ }
+}
+
+/// Inline an `impl`, inherent or of a trait. The `did` must be for an `impl`.
+pub(crate) fn build_impl(
+ cx: &mut DocContext<'_>,
+ parent_module: Option<DefId>,
+ did: DefId,
+ attrs: Option<Attrs<'_>>,
+ ret: &mut Vec<clean::Item>,
+) {
+ if !cx.inlined.insert(did.into()) {
+ return;
+ }
+
+ let _prof_timer = cx.tcx.sess.prof.generic_activity("build_impl");
+
+ let tcx = cx.tcx;
+ let associated_trait = tcx.impl_trait_ref(did);
+
+ // Only inline impl if the implemented trait is
+ // reachable in rustdoc generated documentation
+ if !did.is_local() {
+ if let Some(traitref) = associated_trait {
+ let did = traitref.def_id;
+ if !cx.cache.access_levels.is_public(did) {
+ return;
+ }
+
+ if let Some(stab) = tcx.lookup_stability(did) {
+ if stab.is_unstable() && stab.feature == sym::rustc_private {
+ return;
+ }
+ }
+ }
+ }
+
+ let impl_item = match did.as_local() {
+ Some(did) => match &tcx.hir().expect_item(did).kind {
+ hir::ItemKind::Impl(impl_) => Some(impl_),
+ _ => panic!("`DefID` passed to `build_impl` is not an `impl"),
+ },
+ None => None,
+ };
+
+ let for_ = match &impl_item {
+ Some(impl_) => clean_ty(impl_.self_ty, cx),
+ None => clean_middle_ty(tcx.type_of(did), cx, Some(did)),
+ };
+
+ // Only inline impl if the implementing type is
+ // reachable in rustdoc generated documentation
+ if !did.is_local() {
+ if let Some(did) = for_.def_id(&cx.cache) {
+ if !cx.cache.access_levels.is_public(did) {
+ return;
+ }
+
+ if let Some(stab) = tcx.lookup_stability(did) {
+ if stab.is_unstable() && stab.feature == sym::rustc_private {
+ return;
+ }
+ }
+ }
+ }
+
+ let document_hidden = cx.render_options.document_hidden;
+ let predicates = tcx.explicit_predicates_of(did);
+ let (trait_items, generics) = match impl_item {
+ Some(impl_) => (
+ impl_
+ .items
+ .iter()
+ .map(|item| tcx.hir().impl_item(item.id))
+ .filter(|item| {
+ // Filter out impl items whose corresponding trait item has `doc(hidden)`
+ // not to document such impl items.
+ // For inherent impls, we don't do any filtering, because that's already done in strip_hidden.rs.
+
+ // When `--document-hidden-items` is passed, we don't
+ // do any filtering, too.
+ if document_hidden {
+ return true;
+ }
+ if let Some(associated_trait) = associated_trait {
+ let assoc_kind = match item.kind {
+ hir::ImplItemKind::Const(..) => ty::AssocKind::Const,
+ hir::ImplItemKind::Fn(..) => ty::AssocKind::Fn,
+ hir::ImplItemKind::TyAlias(..) => ty::AssocKind::Type,
+ };
+ let trait_item = tcx
+ .associated_items(associated_trait.def_id)
+ .find_by_name_and_kind(
+ tcx,
+ item.ident,
+ assoc_kind,
+ associated_trait.def_id,
+ )
+ .unwrap(); // SAFETY: For all impl items there exists trait item that has the same name.
+ !tcx.is_doc_hidden(trait_item.def_id)
+ } else {
+ true
+ }
+ })
+ .map(|item| item.clean(cx))
+ .collect::<Vec<_>>(),
+ impl_.generics.clean(cx),
+ ),
+ None => (
+ tcx.associated_items(did)
+ .in_definition_order()
+ .filter(|item| {
+ // If this is a trait impl, filter out associated items whose corresponding item
+ // in the associated trait is marked `doc(hidden)`.
+ // If this is an inherent impl, filter out private associated items.
+ if let Some(associated_trait) = associated_trait {
+ let trait_item = tcx
+ .associated_items(associated_trait.def_id)
+ .find_by_name_and_kind(
+ tcx,
+ item.ident(tcx),
+ item.kind,
+ associated_trait.def_id,
+ )
+ .unwrap(); // corresponding associated item has to exist
+ !tcx.is_doc_hidden(trait_item.def_id)
+ } else {
+ item.visibility(tcx).is_public()
+ }
+ })
+ .map(|item| item.clean(cx))
+ .collect::<Vec<_>>(),
+ clean::enter_impl_trait(cx, |cx| {
+ clean_ty_generics(cx, tcx.generics_of(did), predicates)
+ }),
+ ),
+ };
+ let polarity = tcx.impl_polarity(did);
+ let trait_ = associated_trait.map(|t| clean_trait_ref_with_bindings(cx, t, &[]));
+ if trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() {
+ super::build_deref_target_impls(cx, &trait_items, ret);
+ }
+
+ // Return if the trait itself or any types of the generic parameters are doc(hidden).
+ let mut stack: Vec<&Type> = vec![&for_];
+
+ if let Some(did) = trait_.as_ref().map(|t| t.def_id()) {
+ if tcx.is_doc_hidden(did) {
+ return;
+ }
+ }
+ if let Some(generics) = trait_.as_ref().and_then(|t| t.generics()) {
+ stack.extend(generics);
+ }
+
+ while let Some(ty) = stack.pop() {
+ if let Some(did) = ty.def_id(&cx.cache) {
+ if tcx.is_doc_hidden(did) {
+ return;
+ }
+ }
+ if let Some(generics) = ty.generics() {
+ stack.extend(generics);
+ }
+ }
+
+ if let Some(did) = trait_.as_ref().map(|t| t.def_id()) {
+ record_extern_trait(cx, did);
+ }
+
+ let (merged_attrs, cfg) = merge_attrs(cx, parent_module, load_attrs(cx, did), attrs);
+ trace!("merged_attrs={:?}", merged_attrs);
+
+ trace!(
+ "build_impl: impl {:?} for {:?}",
+ trait_.as_ref().map(|t| t.def_id()),
+ for_.def_id(&cx.cache)
+ );
+ ret.push(clean::Item::from_def_id_and_attrs_and_parts(
+ did,
+ None,
+ clean::ImplItem(Box::new(clean::Impl {
+ unsafety: hir::Unsafety::Normal,
+ generics,
+ trait_,
+ for_,
+ items: trait_items,
+ polarity,
+ kind: if utils::has_doc_flag(tcx, did, sym::fake_variadic) {
+ ImplKind::FakeVaradic
+ } else {
+ ImplKind::Normal
+ },
+ })),
+ Box::new(merged_attrs),
+ cx,
+ cfg,
+ ));
+}
+
+fn build_module(
+ cx: &mut DocContext<'_>,
+ did: DefId,
+ visited: &mut FxHashSet<DefId>,
+) -> clean::Module {
+ let items = build_module_items(cx, did, visited, &mut FxHashSet::default());
+
+ let span = clean::Span::new(cx.tcx.def_span(did));
+ clean::Module { items, span }
+}
+
+fn build_module_items(
+ cx: &mut DocContext<'_>,
+ did: DefId,
+ visited: &mut FxHashSet<DefId>,
+ inlined_names: &mut FxHashSet<(ItemType, Symbol)>,
+) -> Vec<clean::Item> {
+ let mut items = Vec::new();
+
+ // If we're re-exporting a re-export it may actually re-export something in
+ // two namespaces, so the target may be listed twice. Make sure we only
+ // visit each node at most once.
+ for &item in cx.tcx.module_children(did).iter() {
+ if item.vis.is_public() {
+ let res = item.res.expect_non_local();
+ if let Some(def_id) = res.mod_def_id() {
+ // If we're inlining a glob import, it's possible to have
+ // two distinct modules with the same name. We don't want to
+ // inline it, or mark any of its contents as visited.
+ if did == def_id
+ || inlined_names.contains(&(ItemType::Module, item.ident.name))
+ || !visited.insert(def_id)
+ {
+ continue;
+ }
+ }
+ if let Res::PrimTy(p) = res {
+ // Primitive types can't be inlined so generate an import instead.
+ let prim_ty = clean::PrimitiveType::from(p);
+ items.push(clean::Item {
+ name: None,
+ attrs: Box::new(clean::Attributes::default()),
+ item_id: ItemId::Primitive(prim_ty, did.krate),
+ visibility: clean::Public,
+ kind: Box::new(clean::ImportItem(clean::Import::new_simple(
+ item.ident.name,
+ clean::ImportSource {
+ path: clean::Path {
+ res,
+ segments: vec![clean::PathSegment {
+ name: prim_ty.as_sym(),
+ args: clean::GenericArgs::AngleBracketed {
+ args: Default::default(),
+ bindings: ThinVec::new(),
+ },
+ }],
+ },
+ did: None,
+ },
+ true,
+ ))),
+ cfg: None,
+ });
+ } else if let Some(i) = try_inline(cx, did, None, res, item.ident.name, None, visited) {
+ items.extend(i)
+ }
+ }
+ }
+
+ items
+}
+
+pub(crate) fn print_inlined_const(tcx: TyCtxt<'_>, did: DefId) -> String {
+ if let Some(did) = did.as_local() {
+ let hir_id = tcx.hir().local_def_id_to_hir_id(did);
+ rustc_hir_pretty::id_to_string(&tcx.hir(), hir_id)
+ } else {
+ tcx.rendered_const(did).clone()
+ }
+}
+
+fn build_const(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant {
+ clean::Constant {
+ type_: clean_middle_ty(cx.tcx.type_of(def_id), cx, Some(def_id)),
+ kind: clean::ConstantKind::Extern { def_id },
+ }
+}
+
+fn build_static(cx: &mut DocContext<'_>, did: DefId, mutable: bool) -> clean::Static {
+ clean::Static {
+ type_: clean_middle_ty(cx.tcx.type_of(did), cx, Some(did)),
+ mutability: if mutable { Mutability::Mut } else { Mutability::Not },
+ expr: None,
+ }
+}
+
+fn build_macro(
+ cx: &mut DocContext<'_>,
+ def_id: DefId,
+ name: Symbol,
+ import_def_id: Option<DefId>,
+) -> clean::ItemKind {
+ match CStore::from_tcx(cx.tcx).load_macro_untracked(def_id, cx.sess()) {
+ LoadedMacro::MacroDef(item_def, _) => {
+ if let ast::ItemKind::MacroDef(ref def) = item_def.kind {
+ let vis = clean_visibility(cx.tcx.visibility(import_def_id.unwrap_or(def_id)));
+ clean::MacroItem(clean::Macro {
+ source: utils::display_macro_source(cx, name, def, def_id, vis),
+ })
+ } else {
+ unreachable!()
+ }
+ }
+ LoadedMacro::ProcMacro(ext) => clean::ProcMacroItem(clean::ProcMacro {
+ kind: ext.macro_kind(),
+ helpers: ext.helper_attrs,
+ }),
+ }
+}
+
+/// A trait's generics clause actually contains all of the predicates for all of
+/// its associated types as well. We specifically move these clauses to the
+/// associated types instead when displaying, so when we're generating the
+/// generics for the trait itself we need to be sure to remove them.
+/// We also need to remove the implied "recursive" Self: Trait bound.
+///
+/// The inverse of this filtering logic can be found in the `Clean`
+/// implementation for `AssociatedType`
+fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean::Generics {
+ for pred in &mut g.where_predicates {
+ match *pred {
+ clean::WherePredicate::BoundPredicate {
+ ty: clean::Generic(ref s),
+ ref mut bounds,
+ ..
+ } if *s == kw::SelfUpper => {
+ bounds.retain(|bound| match bound {
+ clean::GenericBound::TraitBound(clean::PolyTrait { trait_, .. }, _) => {
+ trait_.def_id() != trait_did
+ }
+ _ => true,
+ });
+ }
+ _ => {}
+ }
+ }
+
+ g.where_predicates.retain(|pred| match pred {
+ clean::WherePredicate::BoundPredicate {
+ ty: clean::QPath { self_type: box clean::Generic(ref s), trait_, .. },
+ bounds,
+ ..
+ } => !(bounds.is_empty() || *s == kw::SelfUpper && trait_.def_id() == trait_did),
+ _ => true,
+ });
+ g
+}
+
+/// Supertrait bounds for a trait are also listed in the generics coming from
+/// the metadata for a crate, so we want to separate those out and create a new
+/// list of explicit supertrait bounds to render nicely.
+fn separate_supertrait_bounds(
+ mut g: clean::Generics,
+) -> (clean::Generics, Vec<clean::GenericBound>) {
+ let mut ty_bounds = Vec::new();
+ g.where_predicates.retain(|pred| match *pred {
+ clean::WherePredicate::BoundPredicate { ty: clean::Generic(ref s), ref bounds, .. }
+ if *s == kw::SelfUpper =>
+ {
+ ty_bounds.extend(bounds.iter().cloned());
+ false
+ }
+ _ => true,
+ });
+ (g, ty_bounds)
+}
+
+pub(crate) fn record_extern_trait(cx: &mut DocContext<'_>, did: DefId) {
+ if did.is_local() {
+ return;
+ }
+
+ {
+ if cx.external_traits.borrow().contains_key(&did) || cx.active_extern_traits.contains(&did)
+ {
+ return;
+ }
+ }
+
+ {
+ cx.active_extern_traits.insert(did);
+ }
+
+ debug!("record_extern_trait: {:?}", did);
+ let trait_ = build_external_trait(cx, did);
+
+ let trait_ = clean::TraitWithExtraInfo {
+ trait_,
+ is_notable: clean::utils::has_doc_flag(cx.tcx, did, sym::notable_trait),
+ };
+ cx.external_traits.borrow_mut().insert(did, trait_);
+ cx.active_extern_traits.remove(&did);
+}
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
new file mode 100644
index 000000000..929f5f89b
--- /dev/null
+++ b/src/librustdoc/clean/mod.rs
@@ -0,0 +1,2242 @@
+//! This module contains the "cleaned" pieces of the AST, and the functions
+//! that clean them.
+
+mod auto_trait;
+mod blanket_impl;
+pub(crate) mod cfg;
+pub(crate) mod inline;
+mod render_macro_matchers;
+mod simplify;
+pub(crate) mod types;
+pub(crate) mod utils;
+
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE};
+use rustc_hir::PredicateOrigin;
+use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData};
+use rustc_middle::middle::resolve_lifetime as rl;
+use rustc_middle::ty::fold::TypeFolder;
+use rustc_middle::ty::subst::{InternalSubsts, Subst};
+use rustc_middle::ty::{self, AdtKind, DefIdTree, EarlyBinder, Lift, Ty, TyCtxt};
+use rustc_middle::{bug, span_bug};
+use rustc_span::hygiene::{AstPass, MacroKind};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{self, ExpnKind};
+use rustc_typeck::hir_ty_to_ty;
+
+use std::assert_matches::assert_matches;
+use std::collections::hash_map::Entry;
+use std::collections::BTreeMap;
+use std::default::Default;
+use std::hash::Hash;
+use std::{mem, vec};
+
+use crate::core::{self, DocContext, ImplTraitParam};
+use crate::formats::item_type::ItemType;
+use crate::visit_ast::Module as DocModule;
+
+use utils::*;
+
+pub(crate) use self::types::*;
+pub(crate) use self::utils::{get_auto_trait_and_blanket_impls, krate, register_res};
+
+pub(crate) trait Clean<'tcx, T> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> T;
+}
+
+impl<'tcx> Clean<'tcx, Item> for DocModule<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Item {
+ let mut items: Vec<Item> = vec![];
+ let mut inserted = FxHashSet::default();
+ items.extend(self.foreigns.iter().map(|(item, renamed)| {
+ let item = clean_maybe_renamed_foreign_item(cx, item, *renamed);
+ if let Some(name) = item.name {
+ inserted.insert((item.type_(), name));
+ }
+ item
+ }));
+ items.extend(self.mods.iter().map(|x| {
+ inserted.insert((ItemType::Module, x.name));
+ x.clean(cx)
+ }));
+
+ // Split up imports from all other items.
+ //
+ // This covers the case where somebody does an import which should pull in an item,
+ // but there's already an item with the same namespace and same name. Rust gives
+ // priority to the not-imported one, so we should, too.
+ items.extend(self.items.iter().flat_map(|(item, renamed)| {
+ // First, lower everything other than imports.
+ if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
+ return Vec::new();
+ }
+ let v = clean_maybe_renamed_item(cx, item, *renamed);
+ for item in &v {
+ if let Some(name) = item.name {
+ inserted.insert((item.type_(), name));
+ }
+ }
+ v
+ }));
+ items.extend(self.items.iter().flat_map(|(item, renamed)| {
+ // Now we actually lower the imports, skipping everything else.
+ if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind {
+ let name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id()));
+ clean_use_statement(item, name, path, hir::UseKind::Glob, cx, &mut inserted)
+ } else {
+ // skip everything else
+ Vec::new()
+ }
+ }));
+
+ // determine if we should display the inner contents or
+ // the outer `mod` item for the source code.
+
+ let span = Span::new({
+ let where_outer = self.where_outer(cx.tcx);
+ let sm = cx.sess().source_map();
+ let outer = sm.lookup_char_pos(where_outer.lo());
+ let inner = sm.lookup_char_pos(self.where_inner.lo());
+ if outer.file.start_pos == inner.file.start_pos {
+ // mod foo { ... }
+ where_outer
+ } else {
+ // mod foo; (and a separate SourceFile for the contents)
+ self.where_inner
+ }
+ });
+
+ Item::from_hir_id_and_parts(
+ self.id,
+ Some(self.name),
+ ModuleItem(Module { items, span }),
+ cx,
+ )
+ }
+}
+
+impl<'tcx> Clean<'tcx, Option<GenericBound>> for hir::GenericBound<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Option<GenericBound> {
+ Some(match *self {
+ hir::GenericBound::Outlives(lt) => GenericBound::Outlives(clean_lifetime(lt, cx)),
+ hir::GenericBound::LangItemTrait(lang_item, span, _, generic_args) => {
+ let def_id = cx.tcx.require_lang_item(lang_item, Some(span));
+
+ let trait_ref = ty::TraitRef::identity(cx.tcx, def_id).skip_binder();
+
+ let generic_args = generic_args.clean(cx);
+ let GenericArgs::AngleBracketed { bindings, .. } = generic_args
+ else {
+ bug!("clean: parenthesized `GenericBound::LangItemTrait`");
+ };
+
+ let trait_ = clean_trait_ref_with_bindings(cx, trait_ref, &bindings);
+ GenericBound::TraitBound(
+ PolyTrait { trait_, generic_params: vec![] },
+ hir::TraitBoundModifier::None,
+ )
+ }
+ hir::GenericBound::Trait(ref t, modifier) => {
+ // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op.
+ if modifier == hir::TraitBoundModifier::MaybeConst
+ && cx.tcx.lang_items().destruct_trait()
+ == Some(t.trait_ref.trait_def_id().unwrap())
+ {
+ return None;
+ }
+
+ GenericBound::TraitBound(t.clean(cx), modifier)
+ }
+ })
+ }
+}
+
+pub(crate) fn clean_trait_ref_with_bindings<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ trait_ref: ty::TraitRef<'tcx>,
+ bindings: &[TypeBinding],
+) -> Path {
+ let kind = cx.tcx.def_kind(trait_ref.def_id).into();
+ if !matches!(kind, ItemType::Trait | ItemType::TraitAlias) {
+ span_bug!(cx.tcx.def_span(trait_ref.def_id), "`TraitRef` had unexpected kind {:?}", kind);
+ }
+ inline::record_extern_fqn(cx, trait_ref.def_id, kind);
+ let path = external_path(cx, trait_ref.def_id, true, bindings.to_vec(), trait_ref.substs);
+
+ debug!("ty::TraitRef\n subst: {:?}\n", trait_ref.substs);
+
+ path
+}
+
+fn clean_poly_trait_ref_with_bindings<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ poly_trait_ref: ty::PolyTraitRef<'tcx>,
+ bindings: &[TypeBinding],
+) -> GenericBound {
+ let poly_trait_ref = poly_trait_ref.lift_to_tcx(cx.tcx).unwrap();
+
+ // collect any late bound regions
+ let late_bound_regions: Vec<_> = cx
+ .tcx
+ .collect_referenced_late_bound_regions(&poly_trait_ref)
+ .into_iter()
+ .filter_map(|br| match br {
+ ty::BrNamed(_, name) if name != kw::UnderscoreLifetime => Some(GenericParamDef {
+ name,
+ kind: GenericParamDefKind::Lifetime { outlives: vec![] },
+ }),
+ _ => None,
+ })
+ .collect();
+
+ let trait_ = clean_trait_ref_with_bindings(cx, poly_trait_ref.skip_binder(), bindings);
+ GenericBound::TraitBound(
+ PolyTrait { trait_, generic_params: late_bound_regions },
+ hir::TraitBoundModifier::None,
+ )
+}
+
+impl<'tcx> Clean<'tcx, GenericBound> for ty::PolyTraitRef<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> GenericBound {
+ clean_poly_trait_ref_with_bindings(cx, *self, &[])
+ }
+}
+
+fn clean_lifetime<'tcx>(lifetime: hir::Lifetime, cx: &mut DocContext<'tcx>) -> Lifetime {
+ let def = cx.tcx.named_region(lifetime.hir_id);
+ if let Some(
+ rl::Region::EarlyBound(_, node_id)
+ | rl::Region::LateBound(_, _, node_id)
+ | rl::Region::Free(_, node_id),
+ ) = def
+ {
+ if let Some(lt) = cx.substs.get(&node_id).and_then(|p| p.as_lt()).cloned() {
+ return lt;
+ }
+ }
+ Lifetime(lifetime.name.ident().name)
+}
+
+pub(crate) fn clean_const<'tcx>(constant: &hir::ConstArg, cx: &mut DocContext<'tcx>) -> Constant {
+ let def_id = cx.tcx.hir().body_owner_def_id(constant.value.body).to_def_id();
+ Constant {
+ type_: clean_middle_ty(cx.tcx.type_of(def_id), cx, Some(def_id)),
+ kind: ConstantKind::Anonymous { body: constant.value.body },
+ }
+}
+
+pub(crate) fn clean_middle_const<'tcx>(
+ constant: ty::Const<'tcx>,
+ cx: &mut DocContext<'tcx>,
+) -> Constant {
+ // FIXME: instead of storing the stringified expression, store `self` directly instead.
+ Constant {
+ type_: clean_middle_ty(constant.ty(), cx, None),
+ kind: ConstantKind::TyConst { expr: constant.to_string() },
+ }
+}
+
+pub(crate) fn clean_middle_region<'tcx>(region: ty::Region<'tcx>) -> Option<Lifetime> {
+ match *region {
+ ty::ReStatic => Some(Lifetime::statik()),
+ ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) => {
+ if name != kw::UnderscoreLifetime { Some(Lifetime(name)) } else { None }
+ }
+ ty::ReEarlyBound(ref data) => {
+ if data.name != kw::UnderscoreLifetime {
+ Some(Lifetime(data.name))
+ } else {
+ None
+ }
+ }
+ ty::ReLateBound(..)
+ | ty::ReFree(..)
+ | ty::ReVar(..)
+ | ty::RePlaceholder(..)
+ | ty::ReEmpty(_)
+ | ty::ReErased => {
+ debug!("cannot clean region {:?}", region);
+ None
+ }
+ }
+}
+
+impl<'tcx> Clean<'tcx, Option<WherePredicate>> for hir::WherePredicate<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Option<WherePredicate> {
+ if !self.in_where_clause() {
+ return None;
+ }
+ Some(match *self {
+ hir::WherePredicate::BoundPredicate(ref wbp) => {
+ let bound_params = wbp
+ .bound_generic_params
+ .iter()
+ .map(|param| {
+ // Higher-ranked params must be lifetimes.
+ // Higher-ranked lifetimes can't have bounds.
+ assert_matches!(
+ param,
+ hir::GenericParam { kind: hir::GenericParamKind::Lifetime { .. }, .. }
+ );
+ Lifetime(param.name.ident().name)
+ })
+ .collect();
+ WherePredicate::BoundPredicate {
+ ty: clean_ty(wbp.bounded_ty, cx),
+ bounds: wbp.bounds.iter().filter_map(|x| x.clean(cx)).collect(),
+ bound_params,
+ }
+ }
+
+ hir::WherePredicate::RegionPredicate(ref wrp) => WherePredicate::RegionPredicate {
+ lifetime: clean_lifetime(wrp.lifetime, cx),
+ bounds: wrp.bounds.iter().filter_map(|x| x.clean(cx)).collect(),
+ },
+
+ hir::WherePredicate::EqPredicate(ref wrp) => WherePredicate::EqPredicate {
+ lhs: clean_ty(wrp.lhs_ty, cx),
+ rhs: clean_ty(wrp.rhs_ty, cx).into(),
+ },
+ })
+ }
+}
+
+impl<'tcx> Clean<'tcx, Option<WherePredicate>> for ty::Predicate<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Option<WherePredicate> {
+ let bound_predicate = self.kind();
+ match bound_predicate.skip_binder() {
+ ty::PredicateKind::Trait(pred) => {
+ clean_poly_trait_predicate(bound_predicate.rebind(pred), cx)
+ }
+ ty::PredicateKind::RegionOutlives(pred) => clean_region_outlives_predicate(pred),
+ ty::PredicateKind::TypeOutlives(pred) => clean_type_outlives_predicate(pred, cx),
+ ty::PredicateKind::Projection(pred) => Some(clean_projection_predicate(pred, cx)),
+ ty::PredicateKind::ConstEvaluatable(..) => None,
+ ty::PredicateKind::WellFormed(..) => None,
+
+ ty::PredicateKind::Subtype(..)
+ | ty::PredicateKind::Coerce(..)
+ | ty::PredicateKind::ObjectSafe(..)
+ | ty::PredicateKind::ClosureKind(..)
+ | ty::PredicateKind::ConstEquate(..)
+ | ty::PredicateKind::TypeWellFormedFromEnv(..) => panic!("not user writable"),
+ }
+ }
+}
+
+fn clean_poly_trait_predicate<'tcx>(
+ pred: ty::PolyTraitPredicate<'tcx>,
+ cx: &mut DocContext<'tcx>,
+) -> Option<WherePredicate> {
+ // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op.
+ if pred.skip_binder().constness == ty::BoundConstness::ConstIfConst
+ && Some(pred.skip_binder().def_id()) == cx.tcx.lang_items().destruct_trait()
+ {
+ return None;
+ }
+
+ let poly_trait_ref = pred.map_bound(|pred| pred.trait_ref);
+ Some(WherePredicate::BoundPredicate {
+ ty: clean_middle_ty(poly_trait_ref.skip_binder().self_ty(), cx, None),
+ bounds: vec![poly_trait_ref.clean(cx)],
+ bound_params: Vec::new(),
+ })
+}
+
+fn clean_region_outlives_predicate<'tcx>(
+ pred: ty::OutlivesPredicate<ty::Region<'tcx>, ty::Region<'tcx>>,
+) -> Option<WherePredicate> {
+ let ty::OutlivesPredicate(a, b) = pred;
+
+ if a.is_empty() && b.is_empty() {
+ return None;
+ }
+
+ Some(WherePredicate::RegionPredicate {
+ lifetime: clean_middle_region(a).expect("failed to clean lifetime"),
+ bounds: vec![GenericBound::Outlives(
+ clean_middle_region(b).expect("failed to clean bounds"),
+ )],
+ })
+}
+
+fn clean_type_outlives_predicate<'tcx>(
+ pred: ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>,
+ cx: &mut DocContext<'tcx>,
+) -> Option<WherePredicate> {
+ let ty::OutlivesPredicate(ty, lt) = pred;
+
+ if lt.is_empty() {
+ return None;
+ }
+
+ Some(WherePredicate::BoundPredicate {
+ ty: clean_middle_ty(ty, cx, None),
+ bounds: vec![GenericBound::Outlives(
+ clean_middle_region(lt).expect("failed to clean lifetimes"),
+ )],
+ bound_params: Vec::new(),
+ })
+}
+
+fn clean_middle_term<'tcx>(term: ty::Term<'tcx>, cx: &mut DocContext<'tcx>) -> Term {
+ match term {
+ ty::Term::Ty(ty) => Term::Type(clean_middle_ty(ty, cx, None)),
+ ty::Term::Const(c) => Term::Constant(clean_middle_const(c, cx)),
+ }
+}
+
+fn clean_hir_term<'tcx>(term: &hir::Term<'tcx>, cx: &mut DocContext<'tcx>) -> Term {
+ match term {
+ hir::Term::Ty(ty) => Term::Type(clean_ty(ty, cx)),
+ hir::Term::Const(c) => {
+ let def_id = cx.tcx.hir().local_def_id(c.hir_id);
+ Term::Constant(clean_middle_const(ty::Const::from_anon_const(cx.tcx, def_id), cx))
+ }
+ }
+}
+
+fn clean_projection_predicate<'tcx>(
+ pred: ty::ProjectionPredicate<'tcx>,
+ cx: &mut DocContext<'tcx>,
+) -> WherePredicate {
+ let ty::ProjectionPredicate { projection_ty, term } = pred;
+ WherePredicate::EqPredicate {
+ lhs: clean_projection(projection_ty, cx, None),
+ rhs: clean_middle_term(term, cx),
+ }
+}
+
+fn clean_projection<'tcx>(
+ ty: ty::ProjectionTy<'tcx>,
+ cx: &mut DocContext<'tcx>,
+ def_id: Option<DefId>,
+) -> Type {
+ let lifted = ty.lift_to_tcx(cx.tcx).unwrap();
+ let trait_ = clean_trait_ref_with_bindings(cx, lifted.trait_ref(cx.tcx), &[]);
+ let self_type = clean_middle_ty(ty.self_ty(), cx, 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 should_show_cast = compute_should_show_cast(self_def_id, &trait_, &self_type);
+ Type::QPath {
+ assoc: Box::new(projection_to_path_segment(ty, cx)),
+ should_show_cast,
+ self_type: Box::new(self_type),
+ trait_,
+ }
+}
+
+fn compute_should_show_cast(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::ProjectionTy<'tcx>,
+ cx: &mut DocContext<'tcx>,
+) -> PathSegment {
+ let item = cx.tcx.associated_item(ty.item_def_id);
+ let generics = cx.tcx.generics_of(ty.item_def_id);
+ PathSegment {
+ name: item.name,
+ args: GenericArgs::AngleBracketed {
+ args: substs_to_args(cx, &ty.substs[generics.parent_count..], false).into(),
+ bindings: Default::default(),
+ },
+ }
+}
+
+fn clean_generic_param_def<'tcx>(
+ def: &ty::GenericParamDef,
+ cx: &mut DocContext<'tcx>,
+) -> GenericParamDef {
+ let (name, kind) = match def.kind {
+ ty::GenericParamDefKind::Lifetime => {
+ (def.name, GenericParamDefKind::Lifetime { outlives: vec![] })
+ }
+ ty::GenericParamDefKind::Type { has_default, synthetic, .. } => {
+ let default = if has_default {
+ Some(clean_middle_ty(cx.tcx.type_of(def.def_id), cx, Some(def.def_id)))
+ } else {
+ None
+ };
+ (
+ def.name,
+ GenericParamDefKind::Type {
+ did: def.def_id,
+ bounds: vec![], // These are filled in from the where-clauses.
+ default: default.map(Box::new),
+ synthetic,
+ },
+ )
+ }
+ ty::GenericParamDefKind::Const { has_default } => (
+ def.name,
+ GenericParamDefKind::Const {
+ did: def.def_id,
+ ty: Box::new(clean_middle_ty(cx.tcx.type_of(def.def_id), cx, Some(def.def_id))),
+ default: match has_default {
+ true => Some(Box::new(cx.tcx.const_param_default(def.def_id).to_string())),
+ false => None,
+ },
+ },
+ ),
+ };
+
+ GenericParamDef { name, kind }
+}
+
+fn clean_generic_param<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ generics: Option<&hir::Generics<'tcx>>,
+ param: &hir::GenericParam<'tcx>,
+) -> GenericParamDef {
+ let did = cx.tcx.hir().local_def_id(param.hir_id);
+ let (name, kind) = match param.kind {
+ hir::GenericParamKind::Lifetime { .. } => {
+ let outlives = if let Some(generics) = generics {
+ generics
+ .outlives_for_param(did)
+ .filter(|bp| !bp.in_where_clause)
+ .flat_map(|bp| bp.bounds)
+ .map(|bound| match bound {
+ hir::GenericBound::Outlives(lt) => clean_lifetime(*lt, cx),
+ _ => panic!(),
+ })
+ .collect()
+ } else {
+ Vec::new()
+ };
+ (param.name.ident().name, GenericParamDefKind::Lifetime { outlives })
+ }
+ hir::GenericParamKind::Type { ref default, synthetic } => {
+ let bounds = if let Some(generics) = generics {
+ generics
+ .bounds_for_param(did)
+ .filter(|bp| bp.origin != PredicateOrigin::WhereClause)
+ .flat_map(|bp| bp.bounds)
+ .filter_map(|x| x.clean(cx))
+ .collect()
+ } else {
+ Vec::new()
+ };
+ (
+ param.name.ident().name,
+ GenericParamDefKind::Type {
+ did: did.to_def_id(),
+ bounds,
+ default: default.map(|t| clean_ty(t, cx)).map(Box::new),
+ synthetic,
+ },
+ )
+ }
+ hir::GenericParamKind::Const { ty, default } => (
+ param.name.ident().name,
+ GenericParamDefKind::Const {
+ did: did.to_def_id(),
+ ty: Box::new(clean_ty(ty, cx)),
+ default: default.map(|ct| {
+ let def_id = cx.tcx.hir().local_def_id(ct.hir_id);
+ Box::new(ty::Const::from_anon_const(cx.tcx, def_id).to_string())
+ }),
+ },
+ ),
+ };
+
+ GenericParamDef { name, kind }
+}
+
+/// Synthetic type-parameters are inserted after normal ones.
+/// In order for normal parameters to be able to refer to synthetic ones,
+/// scans them first.
+fn is_impl_trait(param: &hir::GenericParam<'_>) -> bool {
+ match param.kind {
+ hir::GenericParamKind::Type { synthetic, .. } => synthetic,
+ _ => false,
+ }
+}
+
+/// This can happen for `async fn`, e.g. `async fn f<'_>(&'_ self)`.
+///
+/// See `lifetime_to_generic_param` in `rustc_ast_lowering` for more information.
+fn is_elided_lifetime(param: &hir::GenericParam<'_>) -> bool {
+ matches!(param.kind, hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Elided })
+}
+
+impl<'tcx> Clean<'tcx, Generics> for hir::Generics<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Generics {
+ let impl_trait_params = self
+ .params
+ .iter()
+ .filter(|param| is_impl_trait(param))
+ .map(|param| {
+ let param = clean_generic_param(cx, Some(self), param);
+ match param.kind {
+ GenericParamDefKind::Lifetime { .. } => unreachable!(),
+ GenericParamDefKind::Type { did, ref bounds, .. } => {
+ cx.impl_trait_bounds.insert(did.into(), bounds.clone());
+ }
+ GenericParamDefKind::Const { .. } => unreachable!(),
+ }
+ param
+ })
+ .collect::<Vec<_>>();
+
+ let mut params = Vec::with_capacity(self.params.len());
+ for p in self.params.iter().filter(|p| !is_impl_trait(p) && !is_elided_lifetime(p)) {
+ let p = clean_generic_param(cx, Some(self), p);
+ params.push(p);
+ }
+ params.extend(impl_trait_params);
+
+ let mut generics = Generics {
+ params,
+ where_predicates: self.predicates.iter().filter_map(|x| x.clean(cx)).collect(),
+ };
+
+ // Some duplicates are generated for ?Sized bounds between type params and where
+ // predicates. The point in here is to move the bounds definitions from type params
+ // to where predicates when such cases occur.
+ for where_pred in &mut generics.where_predicates {
+ match *where_pred {
+ WherePredicate::BoundPredicate {
+ ty: Generic(ref name), ref mut bounds, ..
+ } => {
+ if bounds.is_empty() {
+ for param in &mut generics.params {
+ match param.kind {
+ GenericParamDefKind::Lifetime { .. } => {}
+ GenericParamDefKind::Type { bounds: ref mut ty_bounds, .. } => {
+ if &param.name == name {
+ mem::swap(bounds, ty_bounds);
+ break;
+ }
+ }
+ GenericParamDefKind::Const { .. } => {}
+ }
+ }
+ }
+ }
+ _ => continue,
+ }
+ }
+ generics
+ }
+}
+
+fn clean_ty_generics<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ gens: &ty::Generics,
+ preds: ty::GenericPredicates<'tcx>,
+) -> Generics {
+ // Don't populate `cx.impl_trait_bounds` before `clean`ning `where` clauses,
+ // since `Clean for ty::Predicate` would consume them.
+ let mut impl_trait = BTreeMap::<ImplTraitParam, Vec<GenericBound>>::default();
+
+ // Bounds in the type_params and lifetimes fields are repeated in the
+ // predicates field (see rustc_typeck::collect::ty_generics), so remove
+ // them.
+ let stripped_params = gens
+ .params
+ .iter()
+ .filter_map(|param| match param.kind {
+ ty::GenericParamDefKind::Lifetime if param.name == kw::UnderscoreLifetime => None,
+ ty::GenericParamDefKind::Lifetime => Some(clean_generic_param_def(param, cx)),
+ ty::GenericParamDefKind::Type { synthetic, .. } => {
+ if param.name == kw::SelfUpper {
+ assert_eq!(param.index, 0);
+ return None;
+ }
+ if synthetic {
+ impl_trait.insert(param.index.into(), vec![]);
+ return None;
+ }
+ Some(clean_generic_param_def(param, cx))
+ }
+ ty::GenericParamDefKind::Const { .. } => Some(clean_generic_param_def(param, cx)),
+ })
+ .collect::<Vec<GenericParamDef>>();
+
+ // param index -> [(DefId of trait, associated type name and generics, type)]
+ let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, PathSegment, Ty<'_>)>>::default();
+
+ let where_predicates = preds
+ .predicates
+ .iter()
+ .flat_map(|(p, _)| {
+ let mut projection = None;
+ let param_idx = (|| {
+ let bound_p = p.kind();
+ match bound_p.skip_binder() {
+ ty::PredicateKind::Trait(pred) => {
+ if let ty::Param(param) = pred.self_ty().kind() {
+ return Some(param.index);
+ }
+ }
+ ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(ty, _reg)) => {
+ if let ty::Param(param) = ty.kind() {
+ return Some(param.index);
+ }
+ }
+ ty::PredicateKind::Projection(p) => {
+ if let ty::Param(param) = p.projection_ty.self_ty().kind() {
+ projection = Some(bound_p.rebind(p));
+ return Some(param.index);
+ }
+ }
+ _ => (),
+ }
+
+ None
+ })();
+
+ if let Some(param_idx) = param_idx {
+ if let Some(b) = impl_trait.get_mut(&param_idx.into()) {
+ let p: WherePredicate = p.clean(cx)?;
+
+ b.extend(
+ p.get_bounds()
+ .into_iter()
+ .flatten()
+ .cloned()
+ .filter(|b| !b.is_sized_bound(cx)),
+ );
+
+ let proj = projection.map(|p| {
+ (
+ clean_projection(p.skip_binder().projection_ty, cx, None),
+ p.skip_binder().term,
+ )
+ });
+ if let Some(((_, trait_did, name), rhs)) = proj
+ .as_ref()
+ .and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs)))
+ {
+ // FIXME(...): Remove this unwrap()
+ impl_trait_proj.entry(param_idx).or_default().push((
+ trait_did,
+ name,
+ rhs.ty().unwrap(),
+ ));
+ }
+
+ return None;
+ }
+ }
+
+ Some(p)
+ })
+ .collect::<Vec<_>>();
+
+ for (param, mut bounds) in impl_trait {
+ // Move trait bounds to the front.
+ bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..)));
+
+ if let crate::core::ImplTraitParam::ParamIndex(idx) = param {
+ if let Some(proj) = impl_trait_proj.remove(&idx) {
+ for (trait_did, name, rhs) in proj {
+ let rhs = clean_middle_ty(rhs, cx, None);
+ simplify::merge_bounds(cx, &mut bounds, trait_did, name, &Term::Type(rhs));
+ }
+ }
+ } else {
+ unreachable!();
+ }
+
+ cx.impl_trait_bounds.insert(param, bounds);
+ }
+
+ // Now that `cx.impl_trait_bounds` is populated, we can process
+ // remaining predicates which could contain `impl Trait`.
+ let mut where_predicates =
+ where_predicates.into_iter().flat_map(|p| p.clean(cx)).collect::<Vec<_>>();
+
+ // Type parameters have a Sized bound by default unless removed with
+ // ?Sized. Scan through the predicates and mark any type parameter with
+ // a Sized bound, removing the bounds as we find them.
+ //
+ // Note that associated types also have a sized bound by default, but we
+ // don't actually know the set of associated types right here so that's
+ // handled in cleaning associated types
+ let mut sized_params = FxHashSet::default();
+ where_predicates.retain(|pred| match *pred {
+ WherePredicate::BoundPredicate { ty: Generic(ref g), ref bounds, .. } => {
+ if bounds.iter().any(|b| b.is_sized_bound(cx)) {
+ sized_params.insert(*g);
+ false
+ } else {
+ true
+ }
+ }
+ _ => true,
+ });
+
+ // Run through the type parameters again and insert a ?Sized
+ // unbound for any we didn't find to be Sized.
+ for tp in &stripped_params {
+ if matches!(tp.kind, types::GenericParamDefKind::Type { .. })
+ && !sized_params.contains(&tp.name)
+ {
+ where_predicates.push(WherePredicate::BoundPredicate {
+ ty: Type::Generic(tp.name),
+ bounds: vec![GenericBound::maybe_sized(cx)],
+ bound_params: Vec::new(),
+ })
+ }
+ }
+
+ // It would be nice to collect all of the bounds on a type and recombine
+ // them if possible, to avoid e.g., `where T: Foo, T: Bar, T: Sized, T: 'a`
+ // and instead see `where T: Foo + Bar + Sized + 'a`
+
+ Generics {
+ params: stripped_params,
+ where_predicates: simplify::where_clauses(cx, where_predicates),
+ }
+}
+
+fn clean_fn_or_proc_macro<'tcx>(
+ item: &hir::Item<'tcx>,
+ sig: &hir::FnSig<'tcx>,
+ generics: &hir::Generics<'tcx>,
+ body_id: hir::BodyId,
+ name: &mut Symbol,
+ cx: &mut DocContext<'tcx>,
+) -> ItemKind {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let macro_kind = attrs.iter().find_map(|a| {
+ if a.has_name(sym::proc_macro) {
+ Some(MacroKind::Bang)
+ } else if a.has_name(sym::proc_macro_derive) {
+ Some(MacroKind::Derive)
+ } else if a.has_name(sym::proc_macro_attribute) {
+ Some(MacroKind::Attr)
+ } else {
+ None
+ }
+ });
+ match macro_kind {
+ Some(kind) => {
+ if kind == MacroKind::Derive {
+ *name = attrs
+ .lists(sym::proc_macro_derive)
+ .find_map(|mi| mi.ident())
+ .expect("proc-macro derives require a name")
+ .name;
+ }
+
+ let mut helpers = Vec::new();
+ for mi in attrs.lists(sym::proc_macro_derive) {
+ if !mi.has_name(sym::attributes) {
+ continue;
+ }
+
+ if let Some(list) = mi.meta_item_list() {
+ for inner_mi in list {
+ if let Some(ident) = inner_mi.ident() {
+ helpers.push(ident.name);
+ }
+ }
+ }
+ }
+ ProcMacroItem(ProcMacro { kind, helpers })
+ }
+ None => {
+ let mut func = clean_function(cx, sig, generics, body_id);
+ clean_fn_decl_legacy_const_generics(&mut func, attrs);
+ FunctionItem(func)
+ }
+ }
+}
+
+/// This is needed to make it more "readable" when documenting functions using
+/// `rustc_legacy_const_generics`. More information in
+/// <https://github.com/rust-lang/rust/issues/83167>.
+fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[ast::Attribute]) {
+ for meta_item_list in attrs
+ .iter()
+ .filter(|a| a.has_name(sym::rustc_legacy_const_generics))
+ .filter_map(|a| a.meta_item_list())
+ {
+ for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.literal()).enumerate() {
+ match literal.kind {
+ ast::LitKind::Int(a, _) => {
+ let gen = func.generics.params.remove(0);
+ if let GenericParamDef { name, kind: GenericParamDefKind::Const { ty, .. } } =
+ gen
+ {
+ func.decl
+ .inputs
+ .values
+ .insert(a as _, Argument { name, type_: *ty, is_const: true });
+ } else {
+ panic!("unexpected non const in position {pos}");
+ }
+ }
+ _ => panic!("invalid arg index"),
+ }
+ }
+ }
+}
+
+fn clean_function<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ sig: &hir::FnSig<'tcx>,
+ generics: &hir::Generics<'tcx>,
+ body_id: hir::BodyId,
+) -> Box<Function> {
+ let (generics, decl) = enter_impl_trait(cx, |cx| {
+ // NOTE: generics must be cleaned before args
+ let generics = generics.clean(cx);
+ let args = clean_args_from_types_and_body_id(cx, sig.decl.inputs, body_id);
+ let decl = clean_fn_decl_with_args(cx, sig.decl, args);
+ (generics, decl)
+ });
+ Box::new(Function { decl, generics })
+}
+
+fn clean_args_from_types_and_names<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ types: &[hir::Ty<'tcx>],
+ names: &[Ident],
+) -> Arguments {
+ Arguments {
+ values: types
+ .iter()
+ .enumerate()
+ .map(|(i, ty)| {
+ let mut name = names.get(i).map_or(kw::Empty, |ident| ident.name);
+ if name.is_empty() {
+ name = kw::Underscore;
+ }
+ Argument { name, type_: clean_ty(ty, cx), is_const: false }
+ })
+ .collect(),
+ }
+}
+
+fn clean_args_from_types_and_body_id<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ types: &[hir::Ty<'tcx>],
+ body_id: hir::BodyId,
+) -> Arguments {
+ let body = cx.tcx.hir().body(body_id);
+
+ Arguments {
+ values: types
+ .iter()
+ .enumerate()
+ .map(|(i, ty)| Argument {
+ name: name_from_pat(body.params[i].pat),
+ type_: clean_ty(ty, cx),
+ is_const: false,
+ })
+ .collect(),
+ }
+}
+
+fn clean_fn_decl_with_args<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ decl: &hir::FnDecl<'tcx>,
+ args: Arguments,
+) -> FnDecl {
+ let output = match decl.output {
+ hir::FnRetTy::Return(typ) => Return(clean_ty(typ, cx)),
+ hir::FnRetTy::DefaultReturn(..) => DefaultReturn,
+ };
+ FnDecl { inputs: args, output, c_variadic: decl.c_variadic }
+}
+
+fn clean_fn_decl_from_did_and_sig<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ did: Option<DefId>,
+ sig: ty::PolyFnSig<'tcx>,
+) -> FnDecl {
+ let mut names = did.map_or(&[] as &[_], |did| cx.tcx.fn_arg_names(did)).iter();
+
+ // We assume all empty tuples are default return type. This theoretically can discard `-> ()`,
+ // but shouldn't change any code meaning.
+ let output = match clean_middle_ty(sig.skip_binder().output(), cx, None) {
+ Type::Tuple(inner) if inner.is_empty() => DefaultReturn,
+ ty => Return(ty),
+ };
+
+ FnDecl {
+ output,
+ c_variadic: sig.skip_binder().c_variadic,
+ inputs: Arguments {
+ values: sig
+ .skip_binder()
+ .inputs()
+ .iter()
+ .map(|t| Argument {
+ type_: clean_middle_ty(*t, cx, None),
+ name: names.next().map_or(kw::Empty, |i| i.name),
+ is_const: false,
+ })
+ .collect(),
+ },
+ }
+}
+
+fn clean_trait_ref<'tcx>(trait_ref: &hir::TraitRef<'tcx>, cx: &mut DocContext<'tcx>) -> Path {
+ let path = clean_path(trait_ref.path, cx);
+ register_res(cx, path.res);
+ path
+}
+
+impl<'tcx> Clean<'tcx, PolyTrait> for hir::PolyTraitRef<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> PolyTrait {
+ PolyTrait {
+ trait_: clean_trait_ref(&self.trait_ref, cx),
+ generic_params: self
+ .bound_generic_params
+ .iter()
+ .filter(|p| !is_elided_lifetime(p))
+ .map(|x| clean_generic_param(cx, None, x))
+ .collect(),
+ }
+ }
+}
+
+impl<'tcx> Clean<'tcx, Item> for hir::TraitItem<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Item {
+ let local_did = self.def_id.to_def_id();
+ cx.with_param_env(local_did, |cx| {
+ let inner = match self.kind {
+ hir::TraitItemKind::Const(ty, Some(default)) => AssocConstItem(
+ clean_ty(ty, cx),
+ ConstantKind::Local { def_id: local_did, body: default },
+ ),
+ hir::TraitItemKind::Const(ty, None) => TyAssocConstItem(clean_ty(ty, cx)),
+ hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => {
+ let m = clean_function(cx, sig, self.generics, body);
+ MethodItem(m, None)
+ }
+ hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(names)) => {
+ let (generics, decl) = enter_impl_trait(cx, |cx| {
+ // NOTE: generics must be cleaned before args
+ let generics = self.generics.clean(cx);
+ let args = clean_args_from_types_and_names(cx, sig.decl.inputs, names);
+ let decl = clean_fn_decl_with_args(cx, sig.decl, args);
+ (generics, decl)
+ });
+ TyMethodItem(Box::new(Function { decl, generics }))
+ }
+ hir::TraitItemKind::Type(bounds, Some(default)) => {
+ let generics = enter_impl_trait(cx, |cx| self.generics.clean(cx));
+ let bounds = bounds.iter().filter_map(|x| x.clean(cx)).collect();
+ let item_type = clean_middle_ty(hir_ty_to_ty(cx.tcx, default), cx, None);
+ AssocTypeItem(
+ Box::new(Typedef {
+ type_: clean_ty(default, cx),
+ generics,
+ item_type: Some(item_type),
+ }),
+ bounds,
+ )
+ }
+ hir::TraitItemKind::Type(bounds, None) => {
+ let generics = enter_impl_trait(cx, |cx| self.generics.clean(cx));
+ let bounds = bounds.iter().filter_map(|x| x.clean(cx)).collect();
+ TyAssocTypeItem(Box::new(generics), bounds)
+ }
+ };
+ let what_rustc_thinks =
+ Item::from_def_id_and_parts(local_did, Some(self.ident.name), inner, cx);
+ // Trait items always inherit the trait's visibility -- we don't want to show `pub`.
+ Item { visibility: Inherited, ..what_rustc_thinks }
+ })
+ }
+}
+
+impl<'tcx> Clean<'tcx, Item> for hir::ImplItem<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Item {
+ let local_did = self.def_id.to_def_id();
+ cx.with_param_env(local_did, |cx| {
+ let inner = match self.kind {
+ hir::ImplItemKind::Const(ty, expr) => {
+ let default = ConstantKind::Local { def_id: local_did, body: expr };
+ AssocConstItem(clean_ty(ty, cx), default)
+ }
+ hir::ImplItemKind::Fn(ref sig, body) => {
+ let m = clean_function(cx, sig, self.generics, body);
+ let defaultness = cx.tcx.impl_defaultness(self.def_id);
+ MethodItem(m, Some(defaultness))
+ }
+ hir::ImplItemKind::TyAlias(hir_ty) => {
+ let type_ = clean_ty(hir_ty, cx);
+ let generics = self.generics.clean(cx);
+ let item_type = clean_middle_ty(hir_ty_to_ty(cx.tcx, hir_ty), cx, None);
+ AssocTypeItem(
+ Box::new(Typedef { type_, generics, item_type: Some(item_type) }),
+ Vec::new(),
+ )
+ }
+ };
+
+ let mut what_rustc_thinks =
+ Item::from_def_id_and_parts(local_did, Some(self.ident.name), inner, cx);
+
+ let impl_ref = cx.tcx.impl_trait_ref(cx.tcx.local_parent(self.def_id));
+
+ // Trait impl items always inherit the impl's visibility --
+ // we don't want to show `pub`.
+ if impl_ref.is_some() {
+ what_rustc_thinks.visibility = Inherited;
+ }
+
+ what_rustc_thinks
+ })
+ }
+}
+
+impl<'tcx> Clean<'tcx, Item> for ty::AssocItem {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Item {
+ let tcx = cx.tcx;
+ let kind = match self.kind {
+ ty::AssocKind::Const => {
+ let ty = clean_middle_ty(tcx.type_of(self.def_id), cx, Some(self.def_id));
+
+ let provided = match self.container {
+ ty::ImplContainer => true,
+ ty::TraitContainer => tcx.impl_defaultness(self.def_id).has_value(),
+ };
+ if provided {
+ AssocConstItem(ty, ConstantKind::Extern { def_id: self.def_id })
+ } else {
+ TyAssocConstItem(ty)
+ }
+ }
+ ty::AssocKind::Fn => {
+ let generics = clean_ty_generics(
+ cx,
+ tcx.generics_of(self.def_id),
+ tcx.explicit_predicates_of(self.def_id),
+ );
+ let sig = tcx.fn_sig(self.def_id);
+ let mut decl = clean_fn_decl_from_did_and_sig(cx, Some(self.def_id), sig);
+
+ if self.fn_has_self_parameter {
+ let self_ty = match self.container {
+ ty::ImplContainer => tcx.type_of(self.container_id(tcx)),
+ ty::TraitContainer => tcx.types.self_param,
+ };
+ let self_arg_ty = sig.input(0).skip_binder();
+ if self_arg_ty == self_ty {
+ decl.inputs.values[0].type_ = Generic(kw::SelfUpper);
+ } else if let ty::Ref(_, ty, _) = *self_arg_ty.kind() {
+ if ty == self_ty {
+ match decl.inputs.values[0].type_ {
+ BorrowedRef { ref mut type_, .. } => {
+ **type_ = Generic(kw::SelfUpper)
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+ }
+
+ let provided = match self.container {
+ ty::ImplContainer => true,
+ ty::TraitContainer => self.defaultness(tcx).has_value(),
+ };
+ if provided {
+ let defaultness = match self.container {
+ ty::ImplContainer => Some(self.defaultness(tcx)),
+ ty::TraitContainer => None,
+ };
+ MethodItem(Box::new(Function { generics, decl }), defaultness)
+ } else {
+ TyMethodItem(Box::new(Function { generics, decl }))
+ }
+ }
+ ty::AssocKind::Type => {
+ let my_name = self.name;
+
+ fn param_eq_arg(param: &GenericParamDef, arg: &GenericArg) -> bool {
+ match (&param.kind, arg) {
+ (GenericParamDefKind::Type { .. }, GenericArg::Type(Type::Generic(ty)))
+ if *ty == param.name =>
+ {
+ true
+ }
+ (
+ GenericParamDefKind::Lifetime { .. },
+ GenericArg::Lifetime(Lifetime(lt)),
+ ) if *lt == param.name => true,
+ (GenericParamDefKind::Const { .. }, GenericArg::Const(c)) => {
+ match &c.kind {
+ ConstantKind::TyConst { expr } => expr == param.name.as_str(),
+ _ => false,
+ }
+ }
+ _ => false,
+ }
+ }
+
+ if let ty::TraitContainer = self.container {
+ let bounds = tcx.explicit_item_bounds(self.def_id);
+ let predicates = ty::GenericPredicates { parent: None, predicates: bounds };
+ let mut generics =
+ clean_ty_generics(cx, tcx.generics_of(self.def_id), predicates);
+ // Filter out the bounds that are (likely?) directly attached to the associated type,
+ // as opposed to being located in the where clause.
+ let mut bounds = generics
+ .where_predicates
+ .drain_filter(|pred| match *pred {
+ WherePredicate::BoundPredicate {
+ ty: QPath { ref assoc, ref self_type, ref trait_, .. },
+ ..
+ } => {
+ if assoc.name != my_name {
+ return false;
+ }
+ if trait_.def_id() != self.container_id(tcx) {
+ return false;
+ }
+ match **self_type {
+ Generic(ref s) if *s == kw::SelfUpper => {}
+ _ => return false,
+ }
+ match &assoc.args {
+ GenericArgs::AngleBracketed { args, bindings } => {
+ if !bindings.is_empty()
+ || generics
+ .params
+ .iter()
+ .zip(args.iter())
+ .any(|(param, arg)| !param_eq_arg(param, arg))
+ {
+ return false;
+ }
+ }
+ GenericArgs::Parenthesized { .. } => {
+ // The only time this happens is if we're inside the rustdoc for Fn(),
+ // which only has one associated type, which is not a GAT, so whatever.
+ }
+ }
+ true
+ }
+ _ => false,
+ })
+ .flat_map(|pred| {
+ if let WherePredicate::BoundPredicate { bounds, .. } = pred {
+ bounds
+ } else {
+ unreachable!()
+ }
+ })
+ .collect::<Vec<_>>();
+ // Our Sized/?Sized bound didn't get handled when creating the generics
+ // because we didn't actually get our whole set of bounds until just now
+ // (some of them may have come from the trait). If we do have a sized
+ // bound, we remove it, and if we don't then we add the `?Sized` bound
+ // at the end.
+ match bounds.iter().position(|b| b.is_sized_bound(cx)) {
+ Some(i) => {
+ bounds.remove(i);
+ }
+ None => bounds.push(GenericBound::maybe_sized(cx)),
+ }
+
+ if tcx.impl_defaultness(self.def_id).has_value() {
+ AssocTypeItem(
+ Box::new(Typedef {
+ type_: clean_middle_ty(
+ tcx.type_of(self.def_id),
+ cx,
+ Some(self.def_id),
+ ),
+ generics,
+ // FIXME: should we obtain the Type from HIR and pass it on here?
+ item_type: None,
+ }),
+ bounds,
+ )
+ } else {
+ TyAssocTypeItem(Box::new(generics), bounds)
+ }
+ } else {
+ // FIXME: when could this happen? Associated items in inherent impls?
+ AssocTypeItem(
+ Box::new(Typedef {
+ type_: clean_middle_ty(tcx.type_of(self.def_id), cx, Some(self.def_id)),
+ generics: Generics { params: Vec::new(), where_predicates: Vec::new() },
+ item_type: None,
+ }),
+ Vec::new(),
+ )
+ }
+ }
+ };
+
+ let mut what_rustc_thinks =
+ Item::from_def_id_and_parts(self.def_id, Some(self.name), kind, cx);
+
+ let impl_ref = tcx.impl_trait_ref(tcx.parent(self.def_id));
+
+ // Trait impl items always inherit the impl's visibility --
+ // we don't want to show `pub`.
+ if impl_ref.is_some() {
+ what_rustc_thinks.visibility = Visibility::Inherited;
+ }
+
+ what_rustc_thinks
+ }
+}
+
+fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type {
+ let hir::Ty { hir_id: _, span, ref kind } = *hir_ty;
+ let hir::TyKind::Path(qpath) = kind else { unreachable!() };
+
+ match qpath {
+ hir::QPath::Resolved(None, path) => {
+ if let Res::Def(DefKind::TyParam, did) = path.res {
+ if let Some(new_ty) = cx.substs.get(&did).and_then(|p| p.as_ty()).cloned() {
+ return new_ty;
+ }
+ if let Some(bounds) = cx.impl_trait_bounds.remove(&did.into()) {
+ return ImplTrait(bounds);
+ }
+ }
+
+ if let Some(expanded) = maybe_expand_private_type_alias(cx, path) {
+ expanded
+ } else {
+ let path = clean_path(path, cx);
+ resolve_type(cx, path)
+ }
+ }
+ hir::QPath::Resolved(Some(qself), p) => {
+ // Try to normalize `<X as Y>::T` to a type
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let Some(normalized_value) = normalize(cx, ty) {
+ return clean_middle_ty(normalized_value, cx, None);
+ }
+
+ let trait_segments = &p.segments[..p.segments.len() - 1];
+ let trait_def = cx.tcx.associated_item(p.res.def_id()).container_id(cx.tcx);
+ let trait_ = self::Path {
+ res: Res::Def(DefKind::Trait, trait_def),
+ segments: trait_segments.iter().map(|x| x.clean(cx)).collect(),
+ };
+ register_res(cx, trait_.res);
+ let self_def_id = DefId::local(qself.hir_id.owner.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);
+ Type::QPath {
+ assoc: Box::new(p.segments.last().expect("segments were empty").clean(cx)),
+ should_show_cast,
+ self_type: Box::new(self_type),
+ trait_,
+ }
+ }
+ hir::QPath::TypeRelative(qself, segment) => {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ let res = match ty.kind() {
+ ty::Projection(proj) => Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id),
+ // Rustdoc handles `ty::Error`s by turning them into `Type::Infer`s.
+ ty::Error(_) => return Type::Infer,
+ _ => bug!("clean: expected associated type, found `{:?}`", ty),
+ };
+ let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx);
+ register_res(cx, trait_.res);
+ let self_def_id = res.opt_def_id();
+ let self_type = clean_ty(qself, cx);
+ let should_show_cast = compute_should_show_cast(self_def_id, &trait_, &self_type);
+ Type::QPath {
+ assoc: Box::new(segment.clean(cx)),
+ should_show_cast,
+ self_type: Box::new(self_type),
+ trait_,
+ }
+ }
+ hir::QPath::LangItem(..) => bug!("clean: requiring documentation of lang item"),
+ }
+}
+
+fn maybe_expand_private_type_alias<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ path: &hir::Path<'tcx>,
+) -> Option<Type> {
+ let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None };
+ // Substitute private type aliases
+ let def_id = def_id.as_local()?;
+ let alias = if !cx.cache.access_levels.is_exported(def_id.to_def_id()) {
+ &cx.tcx.hir().expect_item(def_id).kind
+ } else {
+ return None;
+ };
+ let hir::ItemKind::TyAlias(ty, generics) = alias else { return None };
+
+ let provided_params = &path.segments.last().expect("segments were empty");
+ let mut substs = FxHashMap::default();
+ let generic_args = provided_params.args();
+
+ let mut indices: hir::GenericParamCount = Default::default();
+ for param in generics.params.iter() {
+ match param.kind {
+ hir::GenericParamKind::Lifetime { .. } => {
+ let mut j = 0;
+ let lifetime = generic_args.args.iter().find_map(|arg| match arg {
+ hir::GenericArg::Lifetime(lt) => {
+ if indices.lifetimes == j {
+ return Some(lt);
+ }
+ j += 1;
+ None
+ }
+ _ => None,
+ });
+ if let Some(lt) = lifetime.cloned() {
+ let lt_def_id = cx.tcx.hir().local_def_id(param.hir_id);
+ let cleaned =
+ if !lt.is_elided() { clean_lifetime(lt, cx) } else { Lifetime::elided() };
+ substs.insert(lt_def_id.to_def_id(), SubstParam::Lifetime(cleaned));
+ }
+ indices.lifetimes += 1;
+ }
+ hir::GenericParamKind::Type { ref default, .. } => {
+ let ty_param_def_id = cx.tcx.hir().local_def_id(param.hir_id);
+ let mut j = 0;
+ let type_ = generic_args.args.iter().find_map(|arg| match arg {
+ hir::GenericArg::Type(ty) => {
+ if indices.types == j {
+ return Some(ty);
+ }
+ j += 1;
+ None
+ }
+ _ => None,
+ });
+ if let Some(ty) = type_ {
+ substs.insert(ty_param_def_id.to_def_id(), SubstParam::Type(clean_ty(ty, cx)));
+ } else if let Some(default) = *default {
+ substs.insert(
+ ty_param_def_id.to_def_id(),
+ SubstParam::Type(clean_ty(default, cx)),
+ );
+ }
+ indices.types += 1;
+ }
+ hir::GenericParamKind::Const { .. } => {
+ let const_param_def_id = cx.tcx.hir().local_def_id(param.hir_id);
+ let mut j = 0;
+ let const_ = generic_args.args.iter().find_map(|arg| match arg {
+ hir::GenericArg::Const(ct) => {
+ if indices.consts == j {
+ return Some(ct);
+ }
+ j += 1;
+ None
+ }
+ _ => None,
+ });
+ if let Some(ct) = const_ {
+ substs.insert(
+ const_param_def_id.to_def_id(),
+ SubstParam::Constant(clean_const(ct, cx)),
+ );
+ }
+ // FIXME(const_generics_defaults)
+ indices.consts += 1;
+ }
+ }
+ }
+
+ Some(cx.enter_alias(substs, |cx| clean_ty(ty, cx)))
+}
+
+pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type {
+ use rustc_hir::*;
+
+ match ty.kind {
+ TyKind::Never => Primitive(PrimitiveType::Never),
+ TyKind::Ptr(ref m) => RawPointer(m.mutbl, Box::new(clean_ty(m.ty, cx))),
+ TyKind::Rptr(ref l, ref m) => {
+ // There are two times a `Fresh` lifetime can be created:
+ // 1. For `&'_ x`, written by the user. This corresponds to `lower_lifetime` in `rustc_ast_lowering`.
+ // 2. For `&x` as a parameter to an `async fn`. This corresponds to `elided_ref_lifetime in `rustc_ast_lowering`.
+ // See #59286 for more information.
+ // Ideally we would only hide the `'_` for case 2., but I don't know a way to distinguish it.
+ // Turning `fn f(&'_ self)` into `fn f(&self)` isn't the worst thing in the world, though;
+ // there's no case where it could cause the function to fail to compile.
+ let elided =
+ l.is_elided() || matches!(l.name, LifetimeName::Param(_, ParamName::Fresh));
+ let lifetime = if elided { None } else { Some(clean_lifetime(*l, cx)) };
+ BorrowedRef { lifetime, mutability: m.mutbl, type_: Box::new(clean_ty(m.ty, cx)) }
+ }
+ TyKind::Slice(ty) => Slice(Box::new(clean_ty(ty, cx))),
+ TyKind::Array(ty, ref length) => {
+ let length = match length {
+ hir::ArrayLen::Infer(_, _) => "_".to_string(),
+ hir::ArrayLen::Body(anon_const) => {
+ let def_id = cx.tcx.hir().local_def_id(anon_const.hir_id);
+ // NOTE(min_const_generics): We can't use `const_eval_poly` for constants
+ // as we currently do not supply the parent generics to anonymous constants
+ // but do allow `ConstKind::Param`.
+ //
+ // `const_eval_poly` tries to to first substitute generic parameters which
+ // results in an ICE while manually constructing the constant and using `eval`
+ // does nothing for `ConstKind::Param`.
+ let ct = ty::Const::from_anon_const(cx.tcx, def_id);
+ let param_env = cx.tcx.param_env(def_id);
+ print_const(cx, ct.eval(cx.tcx, param_env))
+ }
+ };
+
+ Array(Box::new(clean_ty(ty, cx)), length)
+ }
+ TyKind::Tup(tys) => Tuple(tys.iter().map(|ty| clean_ty(ty, cx)).collect()),
+ TyKind::OpaqueDef(item_id, _) => {
+ let item = cx.tcx.hir().item(item_id);
+ if let hir::ItemKind::OpaqueTy(ref ty) = item.kind {
+ ImplTrait(ty.bounds.iter().filter_map(|x| x.clean(cx)).collect())
+ } else {
+ unreachable!()
+ }
+ }
+ TyKind::Path(_) => clean_qpath(ty, cx),
+ TyKind::TraitObject(bounds, ref lifetime, _) => {
+ let bounds = bounds.iter().map(|bound| bound.clean(cx)).collect();
+ let lifetime =
+ if !lifetime.is_elided() { Some(clean_lifetime(*lifetime, cx)) } else { None };
+ DynTrait(bounds, lifetime)
+ }
+ TyKind::BareFn(barefn) => BareFunction(Box::new(barefn.clean(cx))),
+ // Rustdoc handles `TyKind::Err`s by turning them into `Type::Infer`s.
+ TyKind::Infer | TyKind::Err => Infer,
+ TyKind::Typeof(..) => panic!("unimplemented type {:?}", ty.kind),
+ }
+}
+
+/// Returns `None` if the type could not be normalized
+fn normalize<'tcx>(cx: &mut DocContext<'tcx>, ty: Ty<'_>) -> Option<Ty<'tcx>> {
+ // HACK: low-churn fix for #79459 while we wait for a trait normalization fix
+ if !cx.tcx.sess.opts.unstable_opts.normalize_docs {
+ return None;
+ }
+
+ use crate::rustc_trait_selection::infer::TyCtxtInferExt;
+ use crate::rustc_trait_selection::traits::query::normalize::AtExt;
+ use rustc_middle::traits::ObligationCause;
+
+ // Try to normalize `<X as Y>::T` to a type
+ let lifted = ty.lift_to_tcx(cx.tcx).unwrap();
+ let normalized = cx.tcx.infer_ctxt().enter(|infcx| {
+ infcx
+ .at(&ObligationCause::dummy(), cx.param_env)
+ .normalize(lifted)
+ .map(|resolved| infcx.resolve_vars_if_possible(resolved.value))
+ });
+ match normalized {
+ Ok(normalized_value) => {
+ debug!("normalized {:?} to {:?}", ty, normalized_value);
+ Some(normalized_value)
+ }
+ Err(err) => {
+ debug!("failed to normalize {:?}: {:?}", ty, err);
+ None
+ }
+ }
+}
+
+pub(crate) fn clean_middle_ty<'tcx>(
+ this: Ty<'tcx>,
+ cx: &mut DocContext<'tcx>,
+ def_id: Option<DefId>,
+) -> Type {
+ trace!("cleaning type: {:?}", this);
+ let ty = normalize(cx, this).unwrap_or(this);
+ match *ty.kind() {
+ ty::Never => Primitive(PrimitiveType::Never),
+ ty::Bool => Primitive(PrimitiveType::Bool),
+ ty::Char => Primitive(PrimitiveType::Char),
+ ty::Int(int_ty) => Primitive(int_ty.into()),
+ ty::Uint(uint_ty) => Primitive(uint_ty.into()),
+ ty::Float(float_ty) => Primitive(float_ty.into()),
+ ty::Str => Primitive(PrimitiveType::Str),
+ ty::Slice(ty) => Slice(Box::new(clean_middle_ty(ty, cx, None))),
+ ty::Array(ty, n) => {
+ let mut n = cx.tcx.lift(n).expect("array lift failed");
+ n = n.eval(cx.tcx, ty::ParamEnv::reveal_all());
+ let n = print_const(cx, n);
+ Array(Box::new(clean_middle_ty(ty, cx, None)), n)
+ }
+ ty::RawPtr(mt) => RawPointer(mt.mutbl, Box::new(clean_middle_ty(mt.ty, cx, None))),
+ ty::Ref(r, ty, mutbl) => BorrowedRef {
+ lifetime: clean_middle_region(r),
+ mutability: mutbl,
+ type_: Box::new(clean_middle_ty(ty, cx, None)),
+ },
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let ty = cx.tcx.lift(this).expect("FnPtr lift failed");
+ let sig = ty.fn_sig(cx.tcx);
+ let decl = clean_fn_decl_from_did_and_sig(cx, None, sig);
+ BareFunction(Box::new(BareFunctionDecl {
+ unsafety: sig.unsafety(),
+ generic_params: Vec::new(),
+ decl,
+ abi: sig.abi(),
+ }))
+ }
+ ty::Adt(def, substs) => {
+ let did = def.did();
+ let kind = match def.adt_kind() {
+ AdtKind::Struct => ItemType::Struct,
+ AdtKind::Union => ItemType::Union,
+ AdtKind::Enum => ItemType::Enum,
+ };
+ inline::record_extern_fqn(cx, did, kind);
+ let path = external_path(cx, did, false, vec![], substs);
+ Type::Path { path }
+ }
+ ty::Foreign(did) => {
+ inline::record_extern_fqn(cx, did, ItemType::ForeignType);
+ let path = external_path(cx, did, false, vec![], InternalSubsts::empty());
+ Type::Path { path }
+ }
+ ty::Dynamic(obj, ref reg) => {
+ // HACK: pick the first `did` as the `did` of the trait object. Someone
+ // might want to implement "native" support for marker-trait-only
+ // trait objects.
+ let mut dids = obj.principal_def_id().into_iter().chain(obj.auto_traits());
+ let did = dids
+ .next()
+ .unwrap_or_else(|| panic!("found trait object `{:?}` with no traits?", this));
+ let substs = match obj.principal() {
+ Some(principal) => principal.skip_binder().substs,
+ // marker traits have no substs.
+ _ => cx.tcx.intern_substs(&[]),
+ };
+
+ inline::record_extern_fqn(cx, did, ItemType::Trait);
+
+ let lifetime = clean_middle_region(*reg);
+ let mut bounds = vec![];
+
+ for did in dids {
+ let empty = cx.tcx.intern_substs(&[]);
+ let path = external_path(cx, did, false, vec![], empty);
+ inline::record_extern_fqn(cx, did, ItemType::Trait);
+ let bound = PolyTrait { trait_: path, generic_params: Vec::new() };
+ bounds.push(bound);
+ }
+
+ let mut bindings = vec![];
+ for pb in obj.projection_bounds() {
+ bindings.push(TypeBinding {
+ assoc: projection_to_path_segment(
+ pb.skip_binder()
+ .lift_to_tcx(cx.tcx)
+ .unwrap()
+ // HACK(compiler-errors): Doesn't actually matter what self
+ // type we put here, because we're only using the GAT's substs.
+ .with_self_ty(cx.tcx, cx.tcx.types.self_param)
+ .projection_ty,
+ cx,
+ ),
+ kind: TypeBindingKind::Equality {
+ term: clean_middle_term(pb.skip_binder().term, cx),
+ },
+ });
+ }
+
+ let path = external_path(cx, did, false, bindings, substs);
+ bounds.insert(0, PolyTrait { trait_: path, generic_params: Vec::new() });
+
+ DynTrait(bounds, lifetime)
+ }
+ ty::Tuple(t) => Tuple(t.iter().map(|t| clean_middle_ty(t, cx, None)).collect()),
+
+ ty::Projection(ref data) => clean_projection(*data, cx, def_id),
+
+ ty::Param(ref p) => {
+ if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) {
+ ImplTrait(bounds)
+ } else {
+ Generic(p.name)
+ }
+ }
+
+ ty::Opaque(def_id, substs) => {
+ // Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
+ // by looking up the bounds associated with the def_id.
+ let substs = cx.tcx.lift(substs).expect("Opaque lift failed");
+ let bounds = cx
+ .tcx
+ .explicit_item_bounds(def_id)
+ .iter()
+ .map(|(bound, _)| EarlyBinder(*bound).subst(cx.tcx, substs))
+ .collect::<Vec<_>>();
+ let mut regions = vec![];
+ let mut has_sized = false;
+ let mut bounds = bounds
+ .iter()
+ .filter_map(|bound| {
+ let bound_predicate = bound.kind();
+ let trait_ref = match bound_predicate.skip_binder() {
+ ty::PredicateKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref),
+ ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => {
+ if let Some(r) = clean_middle_region(reg) {
+ regions.push(GenericBound::Outlives(r));
+ }
+ return None;
+ }
+ _ => return None,
+ };
+
+ if let Some(sized) = cx.tcx.lang_items().sized_trait() {
+ if trait_ref.def_id() == sized {
+ has_sized = true;
+ return None;
+ }
+ }
+
+ let bindings: Vec<_> = bounds
+ .iter()
+ .filter_map(|bound| {
+ if let ty::PredicateKind::Projection(proj) = bound.kind().skip_binder()
+ {
+ if proj.projection_ty.trait_ref(cx.tcx) == trait_ref.skip_binder() {
+ Some(TypeBinding {
+ assoc: projection_to_path_segment(proj.projection_ty, cx),
+ kind: TypeBindingKind::Equality {
+ term: clean_middle_term(proj.term, cx),
+ },
+ })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Some(clean_poly_trait_ref_with_bindings(cx, trait_ref, &bindings))
+ })
+ .collect::<Vec<_>>();
+ bounds.extend(regions);
+ if !has_sized && !bounds.is_empty() {
+ bounds.insert(0, GenericBound::maybe_sized(cx));
+ }
+ ImplTrait(bounds)
+ }
+
+ ty::Closure(..) => panic!("Closure"),
+ ty::Generator(..) => panic!("Generator"),
+ ty::Bound(..) => panic!("Bound"),
+ ty::Placeholder(..) => panic!("Placeholder"),
+ ty::GeneratorWitness(..) => panic!("GeneratorWitness"),
+ ty::Infer(..) => panic!("Infer"),
+ ty::Error(_) => panic!("Error"),
+ }
+}
+
+pub(crate) fn clean_field<'tcx>(field: &hir::FieldDef<'tcx>, cx: &mut DocContext<'tcx>) -> Item {
+ let def_id = cx.tcx.hir().local_def_id(field.hir_id).to_def_id();
+ clean_field_with_def_id(def_id, field.ident.name, clean_ty(field.ty, cx), cx)
+}
+
+pub(crate) fn clean_middle_field<'tcx>(field: &ty::FieldDef, cx: &mut DocContext<'tcx>) -> Item {
+ clean_field_with_def_id(
+ field.did,
+ field.name,
+ clean_middle_ty(cx.tcx.type_of(field.did), cx, Some(field.did)),
+ cx,
+ )
+}
+
+pub(crate) fn clean_field_with_def_id(
+ def_id: DefId,
+ name: Symbol,
+ ty: Type,
+ cx: &mut DocContext<'_>,
+) -> Item {
+ let what_rustc_thinks =
+ Item::from_def_id_and_parts(def_id, Some(name), StructFieldItem(ty), cx);
+ if is_field_vis_inherited(cx.tcx, def_id) {
+ // Variant fields inherit their enum's visibility.
+ Item { visibility: Visibility::Inherited, ..what_rustc_thinks }
+ } else {
+ what_rustc_thinks
+ }
+}
+
+fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
+ let parent = tcx.parent(def_id);
+ match tcx.def_kind(parent) {
+ DefKind::Struct | DefKind::Union => false,
+ DefKind::Variant => true,
+ parent_kind => panic!("unexpected parent kind: {:?}", parent_kind),
+ }
+}
+
+pub(crate) fn clean_visibility(vis: ty::Visibility) -> Visibility {
+ match vis {
+ ty::Visibility::Public => Visibility::Public,
+ // NOTE: this is not quite right: `ty` uses `Invisible` to mean 'private',
+ // while rustdoc really does mean inherited. That means that for enum variants, such as
+ // `pub enum E { V }`, `V` will be marked as `Public` by `ty`, but as `Inherited` by rustdoc.
+ // Various parts of clean override `tcx.visibility` explicitly to make sure this distinction is captured.
+ ty::Visibility::Invisible => Visibility::Inherited,
+ ty::Visibility::Restricted(module) => Visibility::Restricted(module),
+ }
+}
+
+pub(crate) fn clean_variant_def<'tcx>(variant: &ty::VariantDef, cx: &mut DocContext<'tcx>) -> Item {
+ let kind = match variant.ctor_kind {
+ CtorKind::Const => Variant::CLike,
+ CtorKind::Fn => Variant::Tuple(
+ variant.fields.iter().map(|field| clean_middle_field(field, cx)).collect(),
+ ),
+ CtorKind::Fictive => Variant::Struct(VariantStruct {
+ struct_type: CtorKind::Fictive,
+ fields: variant.fields.iter().map(|field| clean_middle_field(field, cx)).collect(),
+ }),
+ };
+ let what_rustc_thinks =
+ Item::from_def_id_and_parts(variant.def_id, Some(variant.name), VariantItem(kind), cx);
+ // don't show `pub` for variants, which always inherit visibility
+ Item { visibility: Inherited, ..what_rustc_thinks }
+}
+
+fn clean_variant_data<'tcx>(
+ variant: &hir::VariantData<'tcx>,
+ cx: &mut DocContext<'tcx>,
+) -> Variant {
+ match variant {
+ hir::VariantData::Struct(..) => Variant::Struct(VariantStruct {
+ struct_type: CtorKind::from_hir(variant),
+ fields: variant.fields().iter().map(|x| clean_field(x, cx)).collect(),
+ }),
+ hir::VariantData::Tuple(..) => {
+ Variant::Tuple(variant.fields().iter().map(|x| clean_field(x, cx)).collect())
+ }
+ hir::VariantData::Unit(..) => Variant::CLike,
+ }
+}
+
+fn clean_path<'tcx>(path: &hir::Path<'tcx>, cx: &mut DocContext<'tcx>) -> Path {
+ Path { res: path.res, segments: path.segments.iter().map(|x| x.clean(cx)).collect() }
+}
+
+impl<'tcx> Clean<'tcx, GenericArgs> for hir::GenericArgs<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> GenericArgs {
+ if self.parenthesized {
+ let output = clean_ty(self.bindings[0].ty(), cx);
+ let output =
+ if output != Type::Tuple(Vec::new()) { Some(Box::new(output)) } else { None };
+ let inputs = self.inputs().iter().map(|x| clean_ty(x, cx)).collect::<Vec<_>>().into();
+ GenericArgs::Parenthesized { inputs, output }
+ } else {
+ let args = self
+ .args
+ .iter()
+ .map(|arg| match arg {
+ hir::GenericArg::Lifetime(lt) if !lt.is_elided() => {
+ GenericArg::Lifetime(clean_lifetime(*lt, cx))
+ }
+ hir::GenericArg::Lifetime(_) => GenericArg::Lifetime(Lifetime::elided()),
+ hir::GenericArg::Type(ty) => GenericArg::Type(clean_ty(ty, cx)),
+ hir::GenericArg::Const(ct) => GenericArg::Const(Box::new(clean_const(ct, cx))),
+ hir::GenericArg::Infer(_inf) => GenericArg::Infer,
+ })
+ .collect::<Vec<_>>()
+ .into();
+ let bindings =
+ self.bindings.iter().map(|x| clean_type_binding(x, cx)).collect::<Vec<_>>().into();
+ GenericArgs::AngleBracketed { args, bindings }
+ }
+ }
+}
+
+impl<'tcx> Clean<'tcx, PathSegment> for hir::PathSegment<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> PathSegment {
+ PathSegment { name: self.ident.name, args: self.args().clean(cx) }
+ }
+}
+
+impl<'tcx> Clean<'tcx, BareFunctionDecl> for hir::BareFnTy<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> BareFunctionDecl {
+ let (generic_params, decl) = enter_impl_trait(cx, |cx| {
+ // NOTE: generics must be cleaned before args
+ let generic_params = self
+ .generic_params
+ .iter()
+ .filter(|p| !is_elided_lifetime(p))
+ .map(|x| clean_generic_param(cx, None, x))
+ .collect();
+ let args = clean_args_from_types_and_names(cx, self.decl.inputs, self.param_names);
+ let decl = clean_fn_decl_with_args(cx, self.decl, args);
+ (generic_params, decl)
+ });
+ BareFunctionDecl { unsafety: self.unsafety, abi: self.abi, decl, generic_params }
+ }
+}
+
+fn clean_maybe_renamed_item<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ item: &hir::Item<'tcx>,
+ renamed: Option<Symbol>,
+) -> Vec<Item> {
+ use hir::ItemKind;
+
+ let def_id = item.def_id.to_def_id();
+ let mut name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id()));
+ cx.with_param_env(def_id, |cx| {
+ let kind = match item.kind {
+ ItemKind::Static(ty, mutability, body_id) => {
+ StaticItem(Static { type_: clean_ty(ty, cx), mutability, expr: Some(body_id) })
+ }
+ ItemKind::Const(ty, body_id) => ConstantItem(Constant {
+ type_: clean_ty(ty, cx),
+ kind: ConstantKind::Local { body: body_id, def_id },
+ }),
+ ItemKind::OpaqueTy(ref ty) => OpaqueTyItem(OpaqueTy {
+ bounds: ty.bounds.iter().filter_map(|x| x.clean(cx)).collect(),
+ generics: ty.generics.clean(cx),
+ }),
+ ItemKind::TyAlias(hir_ty, generics) => {
+ let rustdoc_ty = clean_ty(hir_ty, cx);
+ let ty = clean_middle_ty(hir_ty_to_ty(cx.tcx, hir_ty), cx, None);
+ TypedefItem(Box::new(Typedef {
+ type_: rustdoc_ty,
+ generics: generics.clean(cx),
+ item_type: Some(ty),
+ }))
+ }
+ ItemKind::Enum(ref def, generics) => EnumItem(Enum {
+ variants: def.variants.iter().map(|v| v.clean(cx)).collect(),
+ generics: generics.clean(cx),
+ }),
+ ItemKind::TraitAlias(generics, bounds) => TraitAliasItem(TraitAlias {
+ generics: generics.clean(cx),
+ bounds: bounds.iter().filter_map(|x| x.clean(cx)).collect(),
+ }),
+ ItemKind::Union(ref variant_data, generics) => UnionItem(Union {
+ generics: generics.clean(cx),
+ fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(),
+ }),
+ ItemKind::Struct(ref variant_data, generics) => StructItem(Struct {
+ struct_type: CtorKind::from_hir(variant_data),
+ generics: generics.clean(cx),
+ fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(),
+ }),
+ ItemKind::Impl(impl_) => return clean_impl(impl_, item.hir_id(), cx),
+ // proc macros can have a name set by attributes
+ ItemKind::Fn(ref sig, generics, body_id) => {
+ clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx)
+ }
+ ItemKind::Macro(ref macro_def, _) => {
+ let ty_vis = clean_visibility(cx.tcx.visibility(def_id));
+ MacroItem(Macro {
+ source: display_macro_source(cx, name, macro_def, def_id, ty_vis),
+ })
+ }
+ ItemKind::Trait(_, _, generics, bounds, item_ids) => {
+ let items =
+ item_ids.iter().map(|ti| cx.tcx.hir().trait_item(ti.id).clean(cx)).collect();
+
+ TraitItem(Trait {
+ def_id,
+ items,
+ generics: generics.clean(cx),
+ bounds: bounds.iter().filter_map(|x| x.clean(cx)).collect(),
+ })
+ }
+ ItemKind::ExternCrate(orig_name) => {
+ return clean_extern_crate(item, name, orig_name, cx);
+ }
+ ItemKind::Use(path, kind) => {
+ return clean_use_statement(item, name, path, kind, cx, &mut FxHashSet::default());
+ }
+ _ => unreachable!("not yet converted"),
+ };
+
+ vec![Item::from_def_id_and_parts(def_id, Some(name), kind, cx)]
+ })
+}
+
+impl<'tcx> Clean<'tcx, Item> for hir::Variant<'tcx> {
+ fn clean(&self, cx: &mut DocContext<'tcx>) -> Item {
+ let kind = VariantItem(clean_variant_data(&self.data, cx));
+ let what_rustc_thinks =
+ Item::from_hir_id_and_parts(self.id, Some(self.ident.name), kind, cx);
+ // don't show `pub` for variants, which are always public
+ Item { visibility: Inherited, ..what_rustc_thinks }
+ }
+}
+
+fn clean_impl<'tcx>(
+ impl_: &hir::Impl<'tcx>,
+ hir_id: hir::HirId,
+ cx: &mut DocContext<'tcx>,
+) -> Vec<Item> {
+ let tcx = cx.tcx;
+ let mut ret = Vec::new();
+ let trait_ = impl_.of_trait.as_ref().map(|t| clean_trait_ref(t, cx));
+ let items =
+ impl_.items.iter().map(|ii| tcx.hir().impl_item(ii.id).clean(cx)).collect::<Vec<_>>();
+ let def_id = tcx.hir().local_def_id(hir_id);
+
+ // If this impl block is an implementation of the Deref trait, then we
+ // need to try inlining the target's inherent impl blocks as well.
+ if trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() {
+ build_deref_target_impls(cx, &items, &mut ret);
+ }
+
+ let for_ = clean_ty(impl_.self_ty, cx);
+ let type_alias = for_.def_id(&cx.cache).and_then(|did| match tcx.def_kind(did) {
+ DefKind::TyAlias => Some(clean_middle_ty(tcx.type_of(did), cx, Some(did))),
+ _ => None,
+ });
+ let mut make_item = |trait_: Option<Path>, for_: Type, items: Vec<Item>| {
+ let kind = ImplItem(Box::new(Impl {
+ unsafety: impl_.unsafety,
+ generics: impl_.generics.clean(cx),
+ trait_,
+ for_,
+ items,
+ polarity: tcx.impl_polarity(def_id),
+ kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), sym::fake_variadic) {
+ ImplKind::FakeVaradic
+ } else {
+ ImplKind::Normal
+ },
+ }));
+ Item::from_hir_id_and_parts(hir_id, None, kind, cx)
+ };
+ if let Some(type_alias) = type_alias {
+ ret.push(make_item(trait_.clone(), type_alias, items.clone()));
+ }
+ ret.push(make_item(trait_, for_, items));
+ ret
+}
+
+fn clean_extern_crate<'tcx>(
+ krate: &hir::Item<'tcx>,
+ name: Symbol,
+ orig_name: Option<Symbol>,
+ cx: &mut DocContext<'tcx>,
+) -> Vec<Item> {
+ // this is the ID of the `extern crate` statement
+ let cnum = cx.tcx.extern_mod_stmt_cnum(krate.def_id).unwrap_or(LOCAL_CRATE);
+ // this is the ID of the crate itself
+ let crate_def_id = cnum.as_def_id();
+ let attrs = cx.tcx.hir().attrs(krate.hir_id());
+ let ty_vis = cx.tcx.visibility(krate.def_id);
+ let please_inline = ty_vis.is_public()
+ && attrs.iter().any(|a| {
+ a.has_name(sym::doc)
+ && match a.meta_item_list() {
+ Some(l) => attr::list_contains_name(&l, sym::inline),
+ None => false,
+ }
+ });
+
+ if please_inline {
+ let mut visited = FxHashSet::default();
+
+ let res = Res::Def(DefKind::Mod, crate_def_id);
+
+ if let Some(items) = inline::try_inline(
+ cx,
+ cx.tcx.parent_module(krate.hir_id()).to_def_id(),
+ Some(krate.def_id.to_def_id()),
+ res,
+ name,
+ Some(attrs),
+ &mut visited,
+ ) {
+ return items;
+ }
+ }
+
+ // FIXME: using `from_def_id_and_kind` breaks `rustdoc/masked` for some reason
+ vec![Item {
+ name: Some(name),
+ attrs: Box::new(Attributes::from_ast(attrs)),
+ item_id: crate_def_id.into(),
+ visibility: clean_visibility(ty_vis),
+ kind: Box::new(ExternCrateItem { src: orig_name }),
+ cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg),
+ }]
+}
+
+fn clean_use_statement<'tcx>(
+ import: &hir::Item<'tcx>,
+ name: Symbol,
+ path: &hir::Path<'tcx>,
+ kind: hir::UseKind,
+ cx: &mut DocContext<'tcx>,
+ inlined_names: &mut FxHashSet<(ItemType, Symbol)>,
+) -> Vec<Item> {
+ // We need this comparison because some imports (for std types for example)
+ // are "inserted" as well but directly by the compiler and they should not be
+ // taken into account.
+ if import.span.ctxt().outer_expn_data().kind == ExpnKind::AstPass(AstPass::StdImports) {
+ return Vec::new();
+ }
+
+ let visibility = cx.tcx.visibility(import.def_id);
+ let attrs = cx.tcx.hir().attrs(import.hir_id());
+ let inline_attr = attrs.lists(sym::doc).get_word_attr(sym::inline);
+ let pub_underscore = visibility.is_public() && name == kw::Underscore;
+ let current_mod = cx.tcx.parent_module_from_def_id(import.def_id);
+
+ // The parent of the module in which this import resides. This
+ // is the same as `current_mod` if that's already the top
+ // level module.
+ let parent_mod = cx.tcx.parent_module_from_def_id(current_mod);
+
+ // This checks if the import can be seen from a higher level module.
+ // In other words, it checks if the visibility is the equivalent of
+ // `pub(super)` or higher. If the current module is the top level
+ // module, there isn't really a parent module, which makes the results
+ // meaningless. In this case, we make sure the answer is `false`.
+ let is_visible_from_parent_mod = visibility.is_accessible_from(parent_mod.to_def_id(), cx.tcx)
+ && !current_mod.is_top_level_module();
+
+ if pub_underscore {
+ if let Some(ref inline) = inline_attr {
+ rustc_errors::struct_span_err!(
+ cx.tcx.sess,
+ inline.span(),
+ E0780,
+ "anonymous imports cannot be inlined"
+ )
+ .span_label(import.span, "anonymous import")
+ .emit();
+ }
+ }
+
+ // We consider inlining the documentation of `pub use` statements, but we
+ // forcefully don't inline if this is not public or if the
+ // #[doc(no_inline)] attribute is present.
+ // Don't inline doc(hidden) imports so they can be stripped at a later stage.
+ let mut denied = cx.output_format.is_json()
+ || !(visibility.is_public()
+ || (cx.render_options.document_private && is_visible_from_parent_mod))
+ || pub_underscore
+ || attrs.iter().any(|a| {
+ a.has_name(sym::doc)
+ && match a.meta_item_list() {
+ Some(l) => {
+ attr::list_contains_name(&l, sym::no_inline)
+ || attr::list_contains_name(&l, sym::hidden)
+ }
+ None => false,
+ }
+ });
+
+ // Also check whether imports were asked to be inlined, in case we're trying to re-export a
+ // crate in Rust 2018+
+ let path = clean_path(path, cx);
+ let inner = if kind == hir::UseKind::Glob {
+ if !denied {
+ let mut visited = FxHashSet::default();
+ if let Some(items) = inline::try_inline_glob(cx, path.res, &mut visited, inlined_names)
+ {
+ return items;
+ }
+ }
+ Import::new_glob(resolve_use_source(cx, path), true)
+ } else {
+ if inline_attr.is_none() {
+ if let Res::Def(DefKind::Mod, did) = path.res {
+ if !did.is_local() && did.is_crate_root() {
+ // if we're `pub use`ing an extern crate root, don't inline it unless we
+ // were specifically asked for it
+ denied = true;
+ }
+ }
+ }
+ if !denied {
+ let mut visited = FxHashSet::default();
+ let import_def_id = import.def_id.to_def_id();
+
+ if let Some(mut items) = inline::try_inline(
+ cx,
+ cx.tcx.parent_module(import.hir_id()).to_def_id(),
+ Some(import_def_id),
+ path.res,
+ name,
+ Some(attrs),
+ &mut visited,
+ ) {
+ items.push(Item::from_def_id_and_parts(
+ import_def_id,
+ None,
+ ImportItem(Import::new_simple(name, resolve_use_source(cx, path), false)),
+ cx,
+ ));
+ return items;
+ }
+ }
+ Import::new_simple(name, resolve_use_source(cx, path), true)
+ };
+
+ vec![Item::from_def_id_and_parts(import.def_id.to_def_id(), None, ImportItem(inner), cx)]
+}
+
+fn clean_maybe_renamed_foreign_item<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ item: &hir::ForeignItem<'tcx>,
+ renamed: Option<Symbol>,
+) -> Item {
+ let def_id = item.def_id.to_def_id();
+ cx.with_param_env(def_id, |cx| {
+ let kind = match item.kind {
+ hir::ForeignItemKind::Fn(decl, names, generics) => {
+ let (generics, decl) = enter_impl_trait(cx, |cx| {
+ // NOTE: generics must be cleaned before args
+ let generics = generics.clean(cx);
+ let args = clean_args_from_types_and_names(cx, decl.inputs, names);
+ let decl = clean_fn_decl_with_args(cx, decl, args);
+ (generics, decl)
+ });
+ ForeignFunctionItem(Box::new(Function { decl, generics }))
+ }
+ hir::ForeignItemKind::Static(ty, mutability) => {
+ ForeignStaticItem(Static { type_: clean_ty(ty, cx), mutability, expr: None })
+ }
+ hir::ForeignItemKind::Type => ForeignTypeItem,
+ };
+
+ Item::from_hir_id_and_parts(
+ item.hir_id(),
+ Some(renamed.unwrap_or(item.ident.name)),
+ kind,
+ cx,
+ )
+ })
+}
+
+fn clean_type_binding<'tcx>(
+ type_binding: &hir::TypeBinding<'tcx>,
+ cx: &mut DocContext<'tcx>,
+) -> TypeBinding {
+ TypeBinding {
+ assoc: PathSegment { name: type_binding.ident.name, args: type_binding.gen_args.clean(cx) },
+ kind: match type_binding.kind {
+ hir::TypeBindingKind::Equality { ref term } => {
+ TypeBindingKind::Equality { term: clean_hir_term(term, cx) }
+ }
+ hir::TypeBindingKind::Constraint { bounds } => TypeBindingKind::Constraint {
+ bounds: bounds.iter().filter_map(|b| b.clean(cx)).collect(),
+ },
+ },
+ }
+}
diff --git a/src/librustdoc/clean/render_macro_matchers.rs b/src/librustdoc/clean/render_macro_matchers.rs
new file mode 100644
index 000000000..ed7683e36
--- /dev/null
+++ b/src/librustdoc/clean/render_macro_matchers.rs
@@ -0,0 +1,238 @@
+use rustc_ast::token::{self, BinOpToken, Delimiter};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_ast_pretty::pprust::state::State as Printer;
+use rustc_ast_pretty::pprust::PrintState;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::parse::ParseSess;
+use rustc_span::source_map::FilePathMapping;
+use rustc_span::symbol::{kw, Ident, Symbol};
+use rustc_span::Span;
+
+/// Render a macro matcher in a format suitable for displaying to the user
+/// as part of an item declaration.
+pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String {
+ if let Some(snippet) = snippet_equal_to_token(tcx, matcher) {
+ // If the original source code is known, we display the matcher exactly
+ // as present in the source code.
+ return snippet;
+ }
+
+ // If the matcher is macro-generated or some other reason the source code
+ // snippet is not available, we attempt to nicely render the token tree.
+ let mut printer = Printer::new();
+
+ // If the inner ibox fits on one line, we get:
+ //
+ // macro_rules! macroname {
+ // (the matcher) => {...};
+ // }
+ //
+ // If the inner ibox gets wrapped, the cbox will break and get indented:
+ //
+ // macro_rules! macroname {
+ // (
+ // the matcher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
+ // ) => {...};
+ // }
+ printer.cbox(8);
+ printer.word("(");
+ printer.zerobreak();
+ printer.ibox(0);
+ match matcher {
+ TokenTree::Delimited(_span, _delim, tts) => print_tts(&mut printer, tts),
+ // Matcher which is not a Delimited is unexpected and should've failed
+ // to compile, but we render whatever it is wrapped in parens.
+ TokenTree::Token(..) => print_tt(&mut printer, matcher),
+ }
+ printer.end();
+ printer.break_offset_if_not_bol(0, -4);
+ printer.word(")");
+ printer.end();
+ printer.s.eof()
+}
+
+/// Find the source snippet for this token's Span, reparse it, and return the
+/// snippet if the reparsed TokenTree matches the argument TokenTree.
+fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option<String> {
+ // Find what rustc thinks is the source snippet.
+ // This may not actually be anything meaningful if this matcher was itself
+ // generated by a macro.
+ let source_map = tcx.sess.source_map();
+ let span = matcher.span();
+ let snippet = source_map.span_to_snippet(span).ok()?;
+
+ // Create a Parser.
+ let sess = ParseSess::new(FilePathMapping::empty());
+ let file_name = source_map.span_to_filename(span);
+ let mut parser =
+ match rustc_parse::maybe_new_parser_from_source_str(&sess, file_name, snippet.clone()) {
+ Ok(parser) => parser,
+ Err(diagnostics) => {
+ drop(diagnostics);
+ return None;
+ }
+ };
+
+ // Reparse a single token tree.
+ let mut reparsed_trees = match parser.parse_all_token_trees() {
+ Ok(reparsed_trees) => reparsed_trees,
+ Err(diagnostic) => {
+ diagnostic.cancel();
+ return None;
+ }
+ };
+ if reparsed_trees.len() != 1 {
+ return None;
+ }
+ let reparsed_tree = reparsed_trees.pop().unwrap();
+
+ // Compare against the original tree.
+ if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
+}
+
+fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
+ match tt {
+ TokenTree::Token(token, _) => {
+ let token_str = printer.token_to_string(token);
+ printer.word(token_str);
+ if let token::DocComment(..) = token.kind {
+ printer.hardbreak()
+ }
+ }
+ TokenTree::Delimited(_span, delim, tts) => {
+ let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim));
+ printer.word(open_delim);
+ if !tts.is_empty() {
+ if *delim == Delimiter::Brace {
+ printer.space();
+ }
+ print_tts(printer, tts);
+ if *delim == Delimiter::Brace {
+ printer.space();
+ }
+ }
+ let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim));
+ printer.word(close_delim);
+ }
+ }
+}
+
+fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
+ #[derive(Copy, Clone, PartialEq)]
+ enum State {
+ Start,
+ Dollar,
+ DollarIdent,
+ DollarIdentColon,
+ DollarParen,
+ DollarParenSep,
+ Pound,
+ PoundBang,
+ Ident,
+ Other,
+ }
+
+ use State::*;
+
+ let mut state = Start;
+ for tt in tts.trees() {
+ let (needs_space, next_state) = match &tt {
+ TokenTree::Token(tt, _) => match (state, &tt.kind) {
+ (Dollar, token::Ident(..)) => (false, DollarIdent),
+ (DollarIdent, token::Colon) => (false, DollarIdentColon),
+ (DollarIdentColon, token::Ident(..)) => (false, Other),
+ (
+ DollarParen,
+ token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
+ ) => (false, Other),
+ (DollarParen, _) => (false, DollarParenSep),
+ (DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
+ (false, Other)
+ }
+ (Pound, token::Not) => (false, PoundBang),
+ (_, token::Ident(symbol, /* is_raw */ false))
+ if !usually_needs_space_between_keyword_and_open_delim(*symbol, tt.span) =>
+ {
+ (true, Ident)
+ }
+ (_, token::Comma | token::Semi) => (false, Other),
+ (_, token::Dollar) => (true, Dollar),
+ (_, token::Pound) => (true, Pound),
+ (_, _) => (true, Other),
+ },
+ TokenTree::Delimited(_, delim, _) => match (state, delim) {
+ (Dollar, Delimiter::Parenthesis) => (false, DollarParen),
+ (Pound | PoundBang, Delimiter::Bracket) => (false, Other),
+ (Ident, Delimiter::Parenthesis | Delimiter::Bracket) => (false, Other),
+ (_, _) => (true, Other),
+ },
+ };
+ if state != Start && needs_space {
+ printer.space();
+ }
+ print_tt(printer, tt);
+ state = next_state;
+ }
+}
+
+fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool {
+ let ident = Ident { name: symbol, span };
+ let is_keyword = ident.is_used_keyword() || ident.is_unused_keyword();
+ if !is_keyword {
+ // An identifier that is not a keyword usually does not need a space
+ // before an open delim. For example: `f(0)` or `f[0]`.
+ return false;
+ }
+
+ match symbol {
+ // No space after keywords that are syntactically an expression. For
+ // example: a tuple struct created with `let _ = Self(0, 0)`, or if
+ // someone has `impl Index<MyStruct> for bool` then `true[MyStruct]`.
+ kw::False | kw::SelfLower | kw::SelfUpper | kw::True => false,
+
+ // No space, as in `let _: fn();`
+ kw::Fn => false,
+
+ // No space, as in `pub(crate) type T;`
+ kw::Pub => false,
+
+ // No space for keywords that can end an expression, as in `fut.await()`
+ // where fut's Output type is `fn()`.
+ kw::Await => false,
+
+ // Otherwise space after keyword. Some examples:
+ //
+ // `expr as [T; 2]`
+ // ^
+ // `box (tuple,)`
+ // ^
+ // `break (tuple,)`
+ // ^
+ // `type T = dyn (Fn() -> dyn Trait) + Send;`
+ // ^
+ // `for (tuple,) in iter {}`
+ // ^
+ // `if (tuple,) == v {}`
+ // ^
+ // `impl [T] {}`
+ // ^
+ // `for x in [..] {}`
+ // ^
+ // `let () = unit;`
+ // ^
+ // `match [x, y] {...}`
+ // ^
+ // `&mut (x as T)`
+ // ^
+ // `return [];`
+ // ^
+ // `fn f<T>() where (): Into<T>`
+ // ^
+ // `while (a + b).what() {}`
+ // ^
+ // `yield [];`
+ // ^
+ _ => true,
+ }
+}
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
new file mode 100644
index 000000000..af7813a77
--- /dev/null
+++ b/src/librustdoc/clean/simplify.rs
@@ -0,0 +1,139 @@
+//! Simplification of where-clauses and parameter bounds into a prettier and
+//! more canonical form.
+//!
+//! Currently all cross-crate-inlined function use `rustc_middle::ty` to reconstruct
+//! the AST (e.g., see all of `clean::inline`), but this is not always a
+//! non-lossy transformation. The current format of storage for where-clauses
+//! for functions and such is simply a list of predicates. One example of this
+//! is that the AST predicate of: `where T: Trait<Foo = Bar>` is encoded as:
+//! `where T: Trait, <T as Trait>::Foo = Bar`.
+//!
+//! This module attempts to reconstruct the original where and/or parameter
+//! bounds by special casing scenarios such as these. Fun!
+
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_hir::def_id::DefId;
+use rustc_middle::ty;
+use rustc_span::Symbol;
+
+use crate::clean;
+use crate::clean::GenericArgs as PP;
+use crate::clean::WherePredicate as WP;
+use crate::core::DocContext;
+
+pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
+ // First, partition the where clause into its separate components.
+ //
+ // We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to
+ // the order of the generated bounds.
+ let mut params: FxIndexMap<Symbol, (Vec<_>, Vec<_>)> = FxIndexMap::default();
+ let mut lifetimes = Vec::new();
+ let mut equalities = Vec::new();
+ let mut tybounds = Vec::new();
+
+ for clause in clauses {
+ match clause {
+ WP::BoundPredicate { ty, bounds, bound_params } => match ty {
+ clean::Generic(s) => {
+ let (b, p) = params.entry(s).or_default();
+ b.extend(bounds);
+ p.extend(bound_params);
+ }
+ t => tybounds.push((t, (bounds, bound_params))),
+ },
+ WP::RegionPredicate { lifetime, bounds } => {
+ lifetimes.push((lifetime, bounds));
+ }
+ WP::EqPredicate { lhs, rhs } => equalities.push((lhs, rhs)),
+ }
+ }
+
+ // Look for equality predicates on associated types that can be merged into
+ // general bound predicates
+ equalities.retain(|&(ref lhs, ref rhs)| {
+ let Some((self_, trait_did, name)) = lhs.projection() else {
+ return true;
+ };
+ let clean::Generic(generic) = self_ else { return true };
+ let Some((bounds, _)) = params.get_mut(generic) else { return true };
+
+ merge_bounds(cx, bounds, trait_did, name, rhs)
+ });
+
+ // And finally, let's reassemble everything
+ let mut clauses = Vec::new();
+ clauses.extend(
+ lifetimes.into_iter().map(|(lt, bounds)| WP::RegionPredicate { lifetime: lt, bounds }),
+ );
+ clauses.extend(params.into_iter().map(|(k, (bounds, params))| WP::BoundPredicate {
+ ty: clean::Generic(k),
+ bounds,
+ bound_params: params,
+ }));
+ clauses.extend(tybounds.into_iter().map(|(ty, (bounds, bound_params))| WP::BoundPredicate {
+ ty,
+ bounds,
+ bound_params,
+ }));
+ clauses.extend(equalities.into_iter().map(|(lhs, rhs)| WP::EqPredicate { lhs, rhs }));
+ clauses
+}
+
+pub(crate) fn merge_bounds(
+ cx: &clean::DocContext<'_>,
+ bounds: &mut Vec<clean::GenericBound>,
+ trait_did: DefId,
+ assoc: clean::PathSegment,
+ rhs: &clean::Term,
+) -> bool {
+ !bounds.iter_mut().any(|b| {
+ let trait_ref = match *b {
+ clean::GenericBound::TraitBound(ref mut tr, _) => tr,
+ clean::GenericBound::Outlives(..) => return false,
+ };
+ // If this QPath's trait `trait_did` is the same as, or a supertrait
+ // of, the bound's trait `did` then we can keep going, otherwise
+ // this is just a plain old equality bound.
+ if !trait_is_same_or_supertrait(cx, trait_ref.trait_.def_id(), trait_did) {
+ return false;
+ }
+ let last = trait_ref.trait_.segments.last_mut().expect("segments were empty");
+ match last.args {
+ PP::AngleBracketed { ref mut bindings, .. } => {
+ bindings.push(clean::TypeBinding {
+ assoc: assoc.clone(),
+ kind: clean::TypeBindingKind::Equality { term: rhs.clone() },
+ });
+ }
+ PP::Parenthesized { ref mut output, .. } => match output {
+ Some(o) => assert_eq!(&clean::Term::Type(o.as_ref().clone()), rhs),
+ None => {
+ if *rhs != clean::Term::Type(clean::Type::Tuple(Vec::new())) {
+ *output = Some(Box::new(rhs.ty().unwrap().clone()));
+ }
+ }
+ },
+ };
+ true
+ })
+}
+
+fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId) -> bool {
+ if child == trait_ {
+ return true;
+ }
+ let predicates = cx.tcx.super_predicates_of(child);
+ debug_assert!(cx.tcx.generics_of(child).has_self);
+ let self_ty = cx.tcx.types.self_param;
+ predicates
+ .predicates
+ .iter()
+ .filter_map(|(pred, _)| {
+ if let ty::PredicateKind::Trait(pred) = pred.kind().skip_binder() {
+ if pred.trait_ref.self_ty() == self_ty { Some(pred.def_id()) } else { None }
+ } else {
+ None
+ }
+ })
+ .any(|did| trait_is_same_or_supertrait(cx, did, trait_))
+}
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
new file mode 100644
index 000000000..0e6de842c
--- /dev/null
+++ b/src/librustdoc/clean/types.rs
@@ -0,0 +1,2508 @@
+use std::cell::RefCell;
+use std::default::Default;
+use std::hash::Hash;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::sync::OnceLock as OnceCell;
+use std::{cmp, fmt, iter};
+
+use arrayvec::ArrayVec;
+
+use rustc_ast::attr;
+use rustc_ast::util::comments::beautify_doc_string;
+use rustc_ast::{self as ast, AttrStyle};
+use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel};
+use rustc_const_eval::const_eval::is_unstable_const_fn;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::thin_vec::ThinVec;
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
+use rustc_hir::lang_items::LangItem;
+use rustc_hir::{BodyId, Mutability};
+use rustc_index::vec::IndexVec;
+use rustc_middle::ty::fast_reject::SimplifiedType;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_session::Session;
+use rustc_span::hygiene::MacroKind;
+use rustc_span::source_map::DUMMY_SP;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{self, FileName, Loc};
+use rustc_target::abi::VariantIdx;
+use rustc_target::spec::abi::Abi;
+use rustc_typeck::check::intrinsic::intrinsic_operation_unsafety;
+
+use crate::clean::cfg::Cfg;
+use crate::clean::clean_visibility;
+use crate::clean::external_path;
+use crate::clean::inline::{self, print_inlined_const};
+use crate::clean::utils::{is_literal_expr, print_const_expr, print_evaluated_const};
+use crate::core::DocContext;
+use crate::formats::cache::Cache;
+use crate::formats::item_type::ItemType;
+use crate::html::render::Context;
+use crate::passes::collect_intra_doc_links::UrlFragment;
+
+pub(crate) use self::FnRetTy::*;
+pub(crate) use self::ItemKind::*;
+pub(crate) use self::SelfTy::*;
+pub(crate) use self::Type::{
+ Array, BareFunction, BorrowedRef, DynTrait, Generic, ImplTrait, Infer, Primitive, QPath,
+ RawPointer, Slice, Tuple,
+};
+pub(crate) use self::Visibility::{Inherited, Public};
+
+#[cfg(test)]
+mod tests;
+
+pub(crate) type ItemIdSet = FxHashSet<ItemId>;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
+pub(crate) enum ItemId {
+ /// A "normal" item that uses a [`DefId`] for identification.
+ DefId(DefId),
+ /// Identifier that is used for auto traits.
+ Auto { trait_: DefId, for_: DefId },
+ /// Identifier that is used for blanket implementations.
+ Blanket { impl_id: DefId, for_: DefId },
+ /// Identifier for primitive types.
+ Primitive(PrimitiveType, CrateNum),
+}
+
+impl ItemId {
+ #[inline]
+ pub(crate) fn is_local(self) -> bool {
+ match self {
+ ItemId::Auto { for_: id, .. }
+ | ItemId::Blanket { for_: id, .. }
+ | ItemId::DefId(id) => id.is_local(),
+ ItemId::Primitive(_, krate) => krate == LOCAL_CRATE,
+ }
+ }
+
+ #[inline]
+ #[track_caller]
+ pub(crate) fn expect_def_id(self) -> DefId {
+ self.as_def_id()
+ .unwrap_or_else(|| panic!("ItemId::expect_def_id: `{:?}` isn't a DefId", self))
+ }
+
+ #[inline]
+ pub(crate) fn as_def_id(self) -> Option<DefId> {
+ match self {
+ ItemId::DefId(id) => Some(id),
+ _ => None,
+ }
+ }
+
+ #[inline]
+ pub(crate) fn krate(self) -> CrateNum {
+ match self {
+ ItemId::Auto { for_: id, .. }
+ | ItemId::Blanket { for_: id, .. }
+ | ItemId::DefId(id) => id.krate,
+ ItemId::Primitive(_, krate) => krate,
+ }
+ }
+}
+
+impl From<DefId> for ItemId {
+ fn from(id: DefId) -> Self {
+ Self::DefId(id)
+ }
+}
+
+/// The crate currently being documented.
+#[derive(Clone, Debug)]
+pub(crate) struct Crate {
+ pub(crate) module: Item,
+ pub(crate) primitives: ThinVec<(DefId, PrimitiveType)>,
+ /// Only here so that they can be filtered through the rustdoc passes.
+ pub(crate) external_traits: Rc<RefCell<FxHashMap<DefId, TraitWithExtraInfo>>>,
+}
+
+impl Crate {
+ pub(crate) fn name(&self, tcx: TyCtxt<'_>) -> Symbol {
+ ExternalCrate::LOCAL.name(tcx)
+ }
+
+ pub(crate) fn src(&self, tcx: TyCtxt<'_>) -> FileName {
+ ExternalCrate::LOCAL.src(tcx)
+ }
+}
+
+/// This struct is used to wrap additional information added by rustdoc on a `trait` item.
+#[derive(Clone, Debug)]
+pub(crate) struct TraitWithExtraInfo {
+ pub(crate) trait_: Trait,
+ pub(crate) is_notable: bool,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct ExternalCrate {
+ pub(crate) crate_num: CrateNum,
+}
+
+impl ExternalCrate {
+ const LOCAL: Self = Self { crate_num: LOCAL_CRATE };
+
+ #[inline]
+ pub(crate) fn def_id(&self) -> DefId {
+ self.crate_num.as_def_id()
+ }
+
+ pub(crate) fn src(&self, tcx: TyCtxt<'_>) -> FileName {
+ let krate_span = tcx.def_span(self.def_id());
+ tcx.sess.source_map().span_to_filename(krate_span)
+ }
+
+ pub(crate) fn name(&self, tcx: TyCtxt<'_>) -> Symbol {
+ tcx.crate_name(self.crate_num)
+ }
+
+ pub(crate) fn src_root(&self, tcx: TyCtxt<'_>) -> PathBuf {
+ match self.src(tcx) {
+ FileName::Real(ref p) => match p.local_path_if_available().parent() {
+ Some(p) => p.to_path_buf(),
+ None => PathBuf::new(),
+ },
+ _ => PathBuf::new(),
+ }
+ }
+
+ /// Attempts to find where an external crate is located, given that we're
+ /// rendering in to the specified source destination.
+ pub(crate) fn location(
+ &self,
+ extern_url: Option<&str>,
+ extern_url_takes_precedence: bool,
+ dst: &std::path::Path,
+ tcx: TyCtxt<'_>,
+ ) -> ExternalLocation {
+ use ExternalLocation::*;
+
+ fn to_remote(url: impl ToString) -> ExternalLocation {
+ let mut url = url.to_string();
+ if !url.ends_with('/') {
+ url.push('/');
+ }
+ Remote(url)
+ }
+
+ // See if there's documentation generated into the local directory
+ // WARNING: since rustdoc creates these directories as it generates documentation, this check is only accurate before rendering starts.
+ // Make sure to call `location()` by that time.
+ let local_location = dst.join(self.name(tcx).as_str());
+ if local_location.is_dir() {
+ return Local;
+ }
+
+ if extern_url_takes_precedence {
+ if let Some(url) = extern_url {
+ return to_remote(url);
+ }
+ }
+
+ // Failing that, see if there's an attribute specifying where to find this
+ // external crate
+ let did = self.crate_num.as_def_id();
+ tcx.get_attrs(did, sym::doc)
+ .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
+ .filter(|a| a.has_name(sym::html_root_url))
+ .filter_map(|a| a.value_str())
+ .map(to_remote)
+ .next()
+ .or_else(|| extern_url.map(to_remote)) // NOTE: only matters if `extern_url_takes_precedence` is false
+ .unwrap_or(Unknown) // Well, at least we tried.
+ }
+
+ pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, Symbol)> {
+ let root = self.def_id();
+
+ let as_keyword = |res: Res<!>| {
+ if let Res::Def(DefKind::Mod, def_id) = res {
+ let mut keyword = None;
+ let meta_items = tcx
+ .get_attrs(def_id, sym::doc)
+ .flat_map(|attr| attr.meta_item_list().unwrap_or_default());
+ for meta in meta_items {
+ if meta.has_name(sym::keyword) {
+ if let Some(v) = meta.value_str() {
+ keyword = Some(v);
+ break;
+ }
+ }
+ }
+ return keyword.map(|p| (def_id, p));
+ }
+ None
+ };
+ if root.is_local() {
+ tcx.hir()
+ .root_module()
+ .item_ids
+ .iter()
+ .filter_map(|&id| {
+ let item = tcx.hir().item(id);
+ match item.kind {
+ hir::ItemKind::Mod(_) => {
+ as_keyword(Res::Def(DefKind::Mod, id.def_id.to_def_id()))
+ }
+ hir::ItemKind::Use(path, hir::UseKind::Single)
+ if tcx.visibility(id.def_id).is_public() =>
+ {
+ as_keyword(path.res.expect_non_local())
+ .map(|(_, prim)| (id.def_id.to_def_id(), prim))
+ }
+ _ => None,
+ }
+ })
+ .collect()
+ } else {
+ tcx.module_children(root).iter().map(|item| item.res).filter_map(as_keyword).collect()
+ }
+ }
+
+ pub(crate) fn primitives(&self, tcx: TyCtxt<'_>) -> ThinVec<(DefId, PrimitiveType)> {
+ let root = self.def_id();
+
+ // Collect all inner modules which are tagged as implementations of
+ // primitives.
+ //
+ // Note that this loop only searches the top-level items of the crate,
+ // and this is intentional. If we were to search the entire crate for an
+ // item tagged with `#[doc(primitive)]` then we would also have to
+ // search the entirety of external modules for items tagged
+ // `#[doc(primitive)]`, which is a pretty inefficient process (decoding
+ // all that metadata unconditionally).
+ //
+ // In order to keep the metadata load under control, the
+ // `#[doc(primitive)]` feature is explicitly designed to only allow the
+ // primitive tags to show up as the top level items in a crate.
+ //
+ // Also note that this does not attempt to deal with modules tagged
+ // duplicately for the same primitive. This is handled later on when
+ // rendering by delegating everything to a hash map.
+ let as_primitive = |res: Res<!>| {
+ if let Res::Def(DefKind::Mod, def_id) = res {
+ let mut prim = None;
+ let meta_items = tcx
+ .get_attrs(def_id, sym::doc)
+ .flat_map(|attr| attr.meta_item_list().unwrap_or_default());
+ for meta in meta_items {
+ if let Some(v) = meta.value_str() {
+ if meta.has_name(sym::primitive) {
+ prim = PrimitiveType::from_symbol(v);
+ if prim.is_some() {
+ break;
+ }
+ // FIXME: should warn on unknown primitives?
+ }
+ }
+ }
+ return prim.map(|p| (def_id, p));
+ }
+ None
+ };
+
+ if root.is_local() {
+ tcx.hir()
+ .root_module()
+ .item_ids
+ .iter()
+ .filter_map(|&id| {
+ let item = tcx.hir().item(id);
+ match item.kind {
+ hir::ItemKind::Mod(_) => {
+ as_primitive(Res::Def(DefKind::Mod, id.def_id.to_def_id()))
+ }
+ hir::ItemKind::Use(path, hir::UseKind::Single)
+ if tcx.visibility(id.def_id).is_public() =>
+ {
+ as_primitive(path.res.expect_non_local()).map(|(_, prim)| {
+ // Pretend the primitive is local.
+ (id.def_id.to_def_id(), prim)
+ })
+ }
+ _ => None,
+ }
+ })
+ .collect()
+ } else {
+ tcx.module_children(root).iter().map(|item| item.res).filter_map(as_primitive).collect()
+ }
+ }
+}
+
+/// Indicates where an external crate can be found.
+#[derive(Debug)]
+pub(crate) enum ExternalLocation {
+ /// Remote URL root of the external crate
+ Remote(String),
+ /// This external crate can be found in the local doc/ folder
+ Local,
+ /// The external crate could not be found.
+ Unknown,
+}
+
+/// Anything with a source location and set of attributes and, optionally, a
+/// name. That is, anything that can be documented. This doesn't correspond
+/// directly to the AST's concept of an item; it's a strict superset.
+#[derive(Clone)]
+pub(crate) struct Item {
+ /// The name of this item.
+ /// Optional because not every item has a name, e.g. impls.
+ pub(crate) name: Option<Symbol>,
+ pub(crate) attrs: Box<Attributes>,
+ pub(crate) visibility: Visibility,
+ /// Information about this item that is specific to what kind of item it is.
+ /// E.g., struct vs enum vs function.
+ pub(crate) kind: Box<ItemKind>,
+ pub(crate) item_id: ItemId,
+
+ pub(crate) cfg: Option<Arc<Cfg>>,
+}
+
+/// NOTE: this does NOT unconditionally print every item, to avoid thousands of lines of logs.
+/// If you want to see the debug output for attributes and the `kind` as well, use `{:#?}` instead of `{:?}`.
+impl fmt::Debug for Item {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let alternate = f.alternate();
+ // hand-picked fields that don't bloat the logs too much
+ let mut fmt = f.debug_struct("Item");
+ fmt.field("name", &self.name)
+ .field("visibility", &self.visibility)
+ .field("item_id", &self.item_id);
+ // allow printing the full item if someone really wants to
+ if alternate {
+ fmt.field("attrs", &self.attrs).field("kind", &self.kind).field("cfg", &self.cfg);
+ } else {
+ fmt.field("kind", &self.type_());
+ fmt.field("docs", &self.doc_value());
+ }
+ fmt.finish()
+ }
+}
+
+pub(crate) fn rustc_span(def_id: DefId, tcx: TyCtxt<'_>) -> Span {
+ Span::new(def_id.as_local().map_or_else(
+ || tcx.def_span(def_id),
+ |local| {
+ let hir = tcx.hir();
+ hir.span_with_body(hir.local_def_id_to_hir_id(local))
+ },
+ ))
+}
+
+impl Item {
+ pub(crate) fn stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option<Stability> {
+ self.item_id.as_def_id().and_then(|did| tcx.lookup_stability(did))
+ }
+
+ pub(crate) fn const_stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option<ConstStability> {
+ self.item_id.as_def_id().and_then(|did| tcx.lookup_const_stability(did))
+ }
+
+ pub(crate) fn deprecation(&self, tcx: TyCtxt<'_>) -> Option<Deprecation> {
+ self.item_id.as_def_id().and_then(|did| tcx.lookup_deprecation(did))
+ }
+
+ pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool {
+ self.item_id
+ .as_def_id()
+ .map(|did| tcx.get_attrs_unchecked(did).inner_docs())
+ .unwrap_or(false)
+ }
+
+ pub(crate) fn span(&self, tcx: TyCtxt<'_>) -> Span {
+ let kind = match &*self.kind {
+ ItemKind::StrippedItem(k) => k,
+ _ => &*self.kind,
+ };
+ match kind {
+ ItemKind::ModuleItem(Module { span, .. }) => *span,
+ ItemKind::ImplItem(box Impl { kind: ImplKind::Auto, .. }) => Span::dummy(),
+ ItemKind::ImplItem(box Impl { kind: ImplKind::Blanket(_), .. }) => {
+ if let ItemId::Blanket { impl_id, .. } = self.item_id {
+ rustc_span(impl_id, tcx)
+ } else {
+ panic!("blanket impl item has non-blanket ID")
+ }
+ }
+ _ => {
+ self.item_id.as_def_id().map(|did| rustc_span(did, tcx)).unwrap_or_else(Span::dummy)
+ }
+ }
+ }
+
+ pub(crate) fn attr_span(&self, tcx: TyCtxt<'_>) -> rustc_span::Span {
+ crate::passes::span_of_attrs(&self.attrs).unwrap_or_else(|| self.span(tcx).inner())
+ }
+
+ /// Finds the `doc` attribute as a NameValue and returns the corresponding
+ /// value found.
+ pub(crate) fn doc_value(&self) -> Option<String> {
+ self.attrs.doc_value()
+ }
+
+ /// Convenience wrapper around [`Self::from_def_id_and_parts`] which converts
+ /// `hir_id` to a [`DefId`]
+ pub(crate) fn from_hir_id_and_parts(
+ hir_id: hir::HirId,
+ name: Option<Symbol>,
+ kind: ItemKind,
+ cx: &mut DocContext<'_>,
+ ) -> Item {
+ Item::from_def_id_and_parts(cx.tcx.hir().local_def_id(hir_id).to_def_id(), name, kind, cx)
+ }
+
+ pub(crate) fn from_def_id_and_parts(
+ def_id: DefId,
+ name: Option<Symbol>,
+ kind: ItemKind,
+ cx: &mut DocContext<'_>,
+ ) -> Item {
+ let ast_attrs = cx.tcx.get_attrs_unchecked(def_id);
+
+ Self::from_def_id_and_attrs_and_parts(
+ def_id,
+ name,
+ kind,
+ Box::new(Attributes::from_ast(ast_attrs)),
+ cx,
+ ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg),
+ )
+ }
+
+ pub(crate) fn from_def_id_and_attrs_and_parts(
+ def_id: DefId,
+ name: Option<Symbol>,
+ kind: ItemKind,
+ attrs: Box<Attributes>,
+ cx: &mut DocContext<'_>,
+ cfg: Option<Arc<Cfg>>,
+ ) -> Item {
+ trace!("name={:?}, def_id={:?}", name, def_id);
+
+ // Primitives and Keywords are written in the source code as private modules.
+ // The modules need to be private so that nobody actually uses them, but the
+ // keywords and primitives that they are documenting are public.
+ let visibility = if matches!(&kind, ItemKind::KeywordItem | ItemKind::PrimitiveItem(..)) {
+ Visibility::Public
+ } else {
+ clean_visibility(cx.tcx.visibility(def_id))
+ };
+
+ Item { item_id: def_id.into(), kind: Box::new(kind), name, attrs, visibility, cfg }
+ }
+
+ /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
+ /// with newlines.
+ pub(crate) fn collapsed_doc_value(&self) -> Option<String> {
+ self.attrs.collapsed_doc_value()
+ }
+
+ pub(crate) fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> {
+ use crate::html::format::href;
+
+ cx.cache()
+ .intra_doc_links
+ .get(&self.item_id)
+ .map_or(&[][..], |v| v.as_slice())
+ .iter()
+ .filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
+ debug!(?did);
+ if let Ok((mut href, ..)) = href(*did, cx) {
+ debug!(?href);
+ if let Some(ref fragment) = *fragment {
+ fragment.render(&mut href, cx.tcx())
+ }
+ Some(RenderedLink {
+ original_text: s.clone(),
+ new_text: link_text.clone(),
+ href,
+ })
+ } else {
+ None
+ }
+ })
+ .collect()
+ }
+
+ /// Find a list of all link names, without finding their href.
+ ///
+ /// This is used for generating summary text, which does not include
+ /// the link text, but does need to know which `[]`-bracketed names
+ /// are actually links.
+ pub(crate) fn link_names(&self, cache: &Cache) -> Vec<RenderedLink> {
+ cache
+ .intra_doc_links
+ .get(&self.item_id)
+ .map_or(&[][..], |v| v.as_slice())
+ .iter()
+ .map(|ItemLink { link: s, link_text, .. }| RenderedLink {
+ original_text: s.clone(),
+ new_text: link_text.clone(),
+ href: String::new(),
+ })
+ .collect()
+ }
+
+ pub(crate) fn is_crate(&self) -> bool {
+ self.is_mod() && self.item_id.as_def_id().map_or(false, |did| did.is_crate_root())
+ }
+ pub(crate) fn is_mod(&self) -> bool {
+ self.type_() == ItemType::Module
+ }
+ pub(crate) fn is_trait(&self) -> bool {
+ self.type_() == ItemType::Trait
+ }
+ pub(crate) fn is_struct(&self) -> bool {
+ self.type_() == ItemType::Struct
+ }
+ pub(crate) fn is_enum(&self) -> bool {
+ self.type_() == ItemType::Enum
+ }
+ pub(crate) fn is_variant(&self) -> bool {
+ self.type_() == ItemType::Variant
+ }
+ pub(crate) fn is_associated_type(&self) -> bool {
+ matches!(&*self.kind, AssocTypeItem(..) | StrippedItem(box AssocTypeItem(..)))
+ }
+ pub(crate) fn is_ty_associated_type(&self) -> bool {
+ matches!(&*self.kind, TyAssocTypeItem(..) | StrippedItem(box TyAssocTypeItem(..)))
+ }
+ pub(crate) fn is_associated_const(&self) -> bool {
+ matches!(&*self.kind, AssocConstItem(..) | StrippedItem(box AssocConstItem(..)))
+ }
+ pub(crate) fn is_ty_associated_const(&self) -> bool {
+ matches!(&*self.kind, TyAssocConstItem(..) | StrippedItem(box TyAssocConstItem(..)))
+ }
+ pub(crate) fn is_method(&self) -> bool {
+ self.type_() == ItemType::Method
+ }
+ pub(crate) fn is_ty_method(&self) -> bool {
+ self.type_() == ItemType::TyMethod
+ }
+ pub(crate) fn is_typedef(&self) -> bool {
+ self.type_() == ItemType::Typedef
+ }
+ pub(crate) fn is_primitive(&self) -> bool {
+ self.type_() == ItemType::Primitive
+ }
+ pub(crate) fn is_union(&self) -> bool {
+ self.type_() == ItemType::Union
+ }
+ pub(crate) fn is_import(&self) -> bool {
+ self.type_() == ItemType::Import
+ }
+ pub(crate) fn is_extern_crate(&self) -> bool {
+ self.type_() == ItemType::ExternCrate
+ }
+ pub(crate) fn is_keyword(&self) -> bool {
+ self.type_() == ItemType::Keyword
+ }
+ pub(crate) fn is_stripped(&self) -> bool {
+ match *self.kind {
+ StrippedItem(..) => true,
+ ImportItem(ref i) => !i.should_be_displayed,
+ _ => false,
+ }
+ }
+ pub(crate) fn has_stripped_entries(&self) -> Option<bool> {
+ match *self.kind {
+ StructItem(ref struct_) => Some(struct_.has_stripped_entries()),
+ UnionItem(ref union_) => Some(union_.has_stripped_entries()),
+ EnumItem(ref enum_) => Some(enum_.has_stripped_entries()),
+ VariantItem(ref v) => v.has_stripped_entries(),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn stability_class(&self, tcx: TyCtxt<'_>) -> Option<String> {
+ self.stability(tcx).as_ref().and_then(|s| {
+ let mut classes = Vec::with_capacity(2);
+
+ if s.is_unstable() {
+ classes.push("unstable");
+ }
+
+ // FIXME: what about non-staged API items that are deprecated?
+ if self.deprecation(tcx).is_some() {
+ classes.push("deprecated");
+ }
+
+ if !classes.is_empty() { Some(classes.join(" ")) } else { None }
+ })
+ }
+
+ pub(crate) fn stable_since(&self, tcx: TyCtxt<'_>) -> Option<Symbol> {
+ match self.stability(tcx)?.level {
+ StabilityLevel::Stable { since, .. } => Some(since),
+ StabilityLevel::Unstable { .. } => None,
+ }
+ }
+
+ pub(crate) fn const_stable_since(&self, tcx: TyCtxt<'_>) -> Option<Symbol> {
+ match self.const_stability(tcx)?.level {
+ StabilityLevel::Stable { since, .. } => Some(since),
+ StabilityLevel::Unstable { .. } => None,
+ }
+ }
+
+ pub(crate) fn is_non_exhaustive(&self) -> bool {
+ self.attrs.other_attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
+ }
+
+ /// Returns a documentation-level item type from the item.
+ pub(crate) fn type_(&self) -> ItemType {
+ ItemType::from(self)
+ }
+
+ pub(crate) fn is_default(&self) -> bool {
+ match *self.kind {
+ ItemKind::MethodItem(_, Some(defaultness)) => {
+ defaultness.has_value() && !defaultness.is_final()
+ }
+ _ => false,
+ }
+ }
+
+ /// Returns a `FnHeader` if `self` is a function item, otherwise returns `None`.
+ pub(crate) fn fn_header(&self, tcx: TyCtxt<'_>) -> Option<hir::FnHeader> {
+ fn build_fn_header(
+ def_id: DefId,
+ tcx: TyCtxt<'_>,
+ asyncness: hir::IsAsync,
+ ) -> hir::FnHeader {
+ let sig = tcx.fn_sig(def_id);
+ let constness =
+ if tcx.is_const_fn(def_id) && is_unstable_const_fn(tcx, def_id).is_none() {
+ hir::Constness::Const
+ } else {
+ hir::Constness::NotConst
+ };
+ hir::FnHeader { unsafety: sig.unsafety(), abi: sig.abi(), constness, asyncness }
+ }
+ let header = match *self.kind {
+ ItemKind::ForeignFunctionItem(_) => {
+ let abi = tcx.fn_sig(self.item_id.as_def_id().unwrap()).abi();
+ hir::FnHeader {
+ unsafety: if abi == Abi::RustIntrinsic {
+ intrinsic_operation_unsafety(self.name.unwrap())
+ } else {
+ hir::Unsafety::Unsafe
+ },
+ abi,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ }
+ }
+ ItemKind::FunctionItem(_) | ItemKind::MethodItem(_, _) => {
+ let def_id = self.item_id.as_def_id().unwrap();
+ build_fn_header(def_id, tcx, tcx.asyncness(def_id))
+ }
+ ItemKind::TyMethodItem(_) => {
+ build_fn_header(self.item_id.as_def_id().unwrap(), tcx, hir::IsAsync::NotAsync)
+ }
+ _ => return None,
+ };
+ Some(header)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum ItemKind {
+ ExternCrateItem {
+ /// The crate's name, *not* the name it's imported as.
+ src: Option<Symbol>,
+ },
+ ImportItem(Import),
+ StructItem(Struct),
+ UnionItem(Union),
+ EnumItem(Enum),
+ FunctionItem(Box<Function>),
+ ModuleItem(Module),
+ TypedefItem(Box<Typedef>),
+ OpaqueTyItem(OpaqueTy),
+ StaticItem(Static),
+ ConstantItem(Constant),
+ TraitItem(Trait),
+ TraitAliasItem(TraitAlias),
+ ImplItem(Box<Impl>),
+ /// A required method in a trait declaration meaning it's only a function signature.
+ TyMethodItem(Box<Function>),
+ /// A method in a trait impl or a provided method in a trait declaration.
+ ///
+ /// Compared to [TyMethodItem], it also contains a method body.
+ MethodItem(Box<Function>, Option<hir::Defaultness>),
+ StructFieldItem(Type),
+ VariantItem(Variant),
+ /// `fn`s from an extern block
+ ForeignFunctionItem(Box<Function>),
+ /// `static`s from an extern block
+ ForeignStaticItem(Static),
+ /// `type`s from an extern block
+ ForeignTypeItem,
+ MacroItem(Macro),
+ ProcMacroItem(ProcMacro),
+ PrimitiveItem(PrimitiveType),
+ /// A required associated constant in a trait declaration.
+ TyAssocConstItem(Type),
+ /// An associated associated constant in a trait impl or a provided one in a trait declaration.
+ AssocConstItem(Type, ConstantKind),
+ /// A required associated type in a trait declaration.
+ ///
+ /// The bounds may be non-empty if there is a `where` clause.
+ TyAssocTypeItem(Box<Generics>, Vec<GenericBound>),
+ /// An associated type in a trait impl or a provided one in a trait declaration.
+ AssocTypeItem(Box<Typedef>, Vec<GenericBound>),
+ /// An item that has been stripped by a rustdoc pass
+ StrippedItem(Box<ItemKind>),
+ KeywordItem,
+}
+
+impl ItemKind {
+ /// Some items contain others such as structs (for their fields) and Enums
+ /// (for their variants). This method returns those contained items.
+ pub(crate) fn inner_items(&self) -> impl Iterator<Item = &Item> {
+ match self {
+ StructItem(s) => s.fields.iter(),
+ UnionItem(u) => u.fields.iter(),
+ VariantItem(Variant::Struct(v)) => v.fields.iter(),
+ VariantItem(Variant::Tuple(v)) => v.iter(),
+ EnumItem(e) => e.variants.iter(),
+ TraitItem(t) => t.items.iter(),
+ ImplItem(i) => i.items.iter(),
+ ModuleItem(m) => m.items.iter(),
+ ExternCrateItem { .. }
+ | ImportItem(_)
+ | FunctionItem(_)
+ | TypedefItem(_)
+ | OpaqueTyItem(_)
+ | StaticItem(_)
+ | ConstantItem(_)
+ | TraitAliasItem(_)
+ | TyMethodItem(_)
+ | MethodItem(_, _)
+ | StructFieldItem(_)
+ | VariantItem(_)
+ | ForeignFunctionItem(_)
+ | ForeignStaticItem(_)
+ | ForeignTypeItem
+ | MacroItem(_)
+ | ProcMacroItem(_)
+ | PrimitiveItem(_)
+ | TyAssocConstItem(_)
+ | AssocConstItem(_, _)
+ | TyAssocTypeItem(..)
+ | AssocTypeItem(..)
+ | StrippedItem(_)
+ | KeywordItem => [].iter(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Module {
+ pub(crate) items: Vec<Item>,
+ pub(crate) span: Span,
+}
+
+pub(crate) trait AttributesExt {
+ type AttributeIterator<'a>: Iterator<Item = ast::NestedMetaItem>
+ where
+ Self: 'a;
+
+ fn lists<'a>(&'a self, name: Symbol) -> Self::AttributeIterator<'a>;
+
+ fn span(&self) -> Option<rustc_span::Span>;
+
+ fn inner_docs(&self) -> bool;
+
+ fn other_attrs(&self) -> Vec<ast::Attribute>;
+
+ fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet<Cfg>) -> Option<Arc<Cfg>>;
+}
+
+impl AttributesExt for [ast::Attribute] {
+ type AttributeIterator<'a> = impl Iterator<Item = ast::NestedMetaItem> + 'a;
+
+ fn lists<'a>(&'a self, name: Symbol) -> Self::AttributeIterator<'a> {
+ self.iter()
+ .filter(move |attr| attr.has_name(name))
+ .filter_map(ast::Attribute::meta_item_list)
+ .flatten()
+ }
+
+ /// Return the span of the first doc-comment, if it exists.
+ fn span(&self) -> Option<rustc_span::Span> {
+ self.iter().find(|attr| attr.doc_str().is_some()).map(|attr| attr.span)
+ }
+
+ /// Returns whether the first doc-comment is an inner attribute.
+ ///
+ //// If there are no doc-comments, return true.
+ /// FIXME(#78591): Support both inner and outer attributes on the same item.
+ fn inner_docs(&self) -> bool {
+ self.iter().find(|a| a.doc_str().is_some()).map_or(true, |a| a.style == AttrStyle::Inner)
+ }
+
+ fn other_attrs(&self) -> Vec<ast::Attribute> {
+ self.iter().filter(|attr| attr.doc_str().is_none()).cloned().collect()
+ }
+
+ fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet<Cfg>) -> Option<Arc<Cfg>> {
+ let sess = tcx.sess;
+ let doc_cfg_active = tcx.features().doc_cfg;
+ let doc_auto_cfg_active = tcx.features().doc_auto_cfg;
+
+ fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
+ let mut iter = it.into_iter();
+ let item = iter.next()?;
+ if iter.next().is_some() {
+ return None;
+ }
+ Some(item)
+ }
+
+ let mut cfg = if doc_cfg_active || doc_auto_cfg_active {
+ let mut doc_cfg = self
+ .iter()
+ .filter(|attr| attr.has_name(sym::doc))
+ .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
+ .filter(|attr| attr.has_name(sym::cfg))
+ .peekable();
+ if doc_cfg.peek().is_some() && doc_cfg_active {
+ doc_cfg
+ .filter_map(|attr| Cfg::parse(attr.meta_item()?).ok())
+ .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg)
+ } else if doc_auto_cfg_active {
+ self.iter()
+ .filter(|attr| attr.has_name(sym::cfg))
+ .filter_map(|attr| single(attr.meta_item_list()?))
+ .filter_map(|attr| {
+ Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten()
+ })
+ .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg)
+ } else {
+ Cfg::True
+ }
+ } else {
+ Cfg::True
+ };
+
+ for attr in self.iter() {
+ // #[doc]
+ if attr.doc_str().is_none() && attr.has_name(sym::doc) {
+ // #[doc(...)]
+ if let Some(list) = attr.meta().as_ref().and_then(|mi| mi.meta_item_list()) {
+ for item in list {
+ // #[doc(hidden)]
+ if !item.has_name(sym::cfg) {
+ continue;
+ }
+ // #[doc(cfg(...))]
+ if let Some(cfg_mi) = item
+ .meta_item()
+ .and_then(|item| rustc_expand::config::parse_cfg(item, sess))
+ {
+ match Cfg::parse(cfg_mi) {
+ Ok(new_cfg) => cfg &= new_cfg,
+ Err(e) => {
+ sess.span_err(e.span, e.msg);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // treat #[target_feature(enable = "feat")] attributes as if they were
+ // #[doc(cfg(target_feature = "feat"))] attributes as well
+ for attr in self.lists(sym::target_feature) {
+ if attr.has_name(sym::enable) {
+ if let Some(feat) = attr.value_str() {
+ let meta = attr::mk_name_value_item_str(
+ Ident::with_dummy_span(sym::target_feature),
+ feat,
+ DUMMY_SP,
+ );
+ if let Ok(feat_cfg) = Cfg::parse(&meta) {
+ cfg &= feat_cfg;
+ }
+ }
+ }
+ }
+
+ if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) }
+ }
+}
+
+pub(crate) trait NestedAttributesExt {
+ /// Returns `true` if the attribute list contains a specific `word`
+ fn has_word(self, word: Symbol) -> bool
+ where
+ Self: std::marker::Sized,
+ {
+ <Self as NestedAttributesExt>::get_word_attr(self, word).is_some()
+ }
+
+ /// Returns `Some(attr)` if the attribute list contains 'attr'
+ /// corresponding to a specific `word`
+ fn get_word_attr(self, word: Symbol) -> Option<ast::NestedMetaItem>;
+}
+
+impl<I: Iterator<Item = ast::NestedMetaItem>> NestedAttributesExt for I {
+ fn get_word_attr(mut self, word: Symbol) -> Option<ast::NestedMetaItem> {
+ self.find(|attr| attr.is_word() && attr.has_name(word))
+ }
+}
+
+/// A portion of documentation, extracted from a `#[doc]` attribute.
+///
+/// Each variant contains the line number within the complete doc-comment where the fragment
+/// starts, as well as the Span where the corresponding doc comment or attribute is located.
+///
+/// Included files are kept separate from inline doc comments so that proper line-number
+/// information can be given when a doctest fails. Sugared doc comments and "raw" doc comments are
+/// kept separate because of issue #42760.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub(crate) struct DocFragment {
+ pub(crate) span: rustc_span::Span,
+ /// The module this doc-comment came from.
+ ///
+ /// This allows distinguishing between the original documentation and a pub re-export.
+ /// If it is `None`, the item was not re-exported.
+ pub(crate) parent_module: Option<DefId>,
+ pub(crate) doc: Symbol,
+ pub(crate) kind: DocFragmentKind,
+ pub(crate) indent: usize,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub(crate) enum DocFragmentKind {
+ /// A doc fragment created from a `///` or `//!` doc comment.
+ SugaredDoc,
+ /// A doc fragment created from a "raw" `#[doc=""]` attribute.
+ RawDoc,
+}
+
+/// The goal of this function is to apply the `DocFragment` transformation that is required when
+/// transforming into the final Markdown, which is applying the computed indent to each line in
+/// each doc fragment (a `DocFragment` can contain multiple lines in case of `#[doc = ""]`).
+///
+/// Note: remove the trailing newline where appropriate
+fn add_doc_fragment(out: &mut String, frag: &DocFragment) {
+ let s = frag.doc.as_str();
+ let mut iter = s.lines();
+ if s.is_empty() {
+ out.push('\n');
+ return;
+ }
+ while let Some(line) = iter.next() {
+ if line.chars().any(|c| !c.is_whitespace()) {
+ assert!(line.len() >= frag.indent);
+ out.push_str(&line[frag.indent..]);
+ } else {
+ out.push_str(line);
+ }
+ out.push('\n');
+ }
+}
+
+/// Collapse a collection of [`DocFragment`]s into one string,
+/// handling indentation and newlines as needed.
+pub(crate) fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String {
+ let mut acc = String::new();
+ for frag in doc_strings {
+ add_doc_fragment(&mut acc, frag);
+ }
+ acc.pop();
+ acc
+}
+
+/// Removes excess indentation on comments in order for the Markdown
+/// to be parsed correctly. This is necessary because the convention for
+/// writing documentation is to provide a space between the /// or //! marker
+/// and the doc text, but Markdown is whitespace-sensitive. For example,
+/// a block of text with four-space indentation is parsed as a code block,
+/// so if we didn't unindent comments, these list items
+///
+/// /// A list:
+/// ///
+/// /// - Foo
+/// /// - Bar
+///
+/// would be parsed as if they were in a code block, which is likely not what the user intended.
+fn unindent_doc_fragments(docs: &mut Vec<DocFragment>) {
+ // `add` is used in case the most common sugared doc syntax is used ("/// "). The other
+ // fragments kind's lines are never starting with a whitespace unless they are using some
+ // markdown formatting requiring it. Therefore, if the doc block have a mix between the two,
+ // we need to take into account the fact that the minimum indent minus one (to take this
+ // whitespace into account).
+ //
+ // For example:
+ //
+ // /// hello!
+ // #[doc = "another"]
+ //
+ // In this case, you want "hello! another" and not "hello! another".
+ let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind)
+ && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc)
+ {
+ // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
+ // "decide" how much the minimum indent will be.
+ 1
+ } else {
+ 0
+ };
+
+ // `min_indent` is used to know how much whitespaces from the start of each lines must be
+ // removed. Example:
+ //
+ // /// hello!
+ // #[doc = "another"]
+ //
+ // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum
+ // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4
+ // (5 - 1) whitespaces.
+ let Some(min_indent) = docs
+ .iter()
+ .map(|fragment| {
+ fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| {
+ if line.chars().all(|c| c.is_whitespace()) {
+ min_indent
+ } else {
+ // Compare against either space or tab, ignoring whether they are
+ // mixed or not.
+ let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
+ cmp::min(min_indent, whitespace)
+ + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }
+ }
+ })
+ })
+ .min()
+ else {
+ return;
+ };
+
+ for fragment in docs {
+ if fragment.doc == kw::Empty {
+ continue;
+ }
+
+ let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 {
+ min_indent - add
+ } else {
+ min_indent
+ };
+
+ fragment.indent = min_indent;
+ }
+}
+
+/// A link that has not yet been rendered.
+///
+/// This link will be turned into a rendered link by [`Item::links`].
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) struct ItemLink {
+ /// The original link written in the markdown
+ pub(crate) link: String,
+ /// The link text displayed in the HTML.
+ ///
+ /// This may not be the same as `link` if there was a disambiguator
+ /// in an intra-doc link (e.g. \[`fn@f`\])
+ pub(crate) link_text: String,
+ pub(crate) did: DefId,
+ /// The url fragment to append to the link
+ pub(crate) fragment: Option<UrlFragment>,
+}
+
+pub struct RenderedLink {
+ /// The text the link was original written as.
+ ///
+ /// This could potentially include disambiguators and backticks.
+ pub(crate) original_text: String,
+ /// The text to display in the HTML
+ pub(crate) new_text: String,
+ /// The URL to put in the `href`
+ pub(crate) href: String,
+}
+
+/// The attributes on an [`Item`], including attributes like `#[derive(...)]` and `#[inline]`,
+/// as well as doc comments.
+#[derive(Clone, Debug, Default)]
+pub(crate) struct Attributes {
+ pub(crate) doc_strings: Vec<DocFragment>,
+ pub(crate) other_attrs: Vec<ast::Attribute>,
+}
+
+impl Attributes {
+ pub(crate) fn lists(&self, name: Symbol) -> impl Iterator<Item = ast::NestedMetaItem> + '_ {
+ self.other_attrs.lists(name)
+ }
+
+ pub(crate) fn has_doc_flag(&self, flag: Symbol) -> bool {
+ for attr in &self.other_attrs {
+ if !attr.has_name(sym::doc) {
+ continue;
+ }
+
+ if let Some(items) = attr.meta_item_list() {
+ if items.iter().filter_map(|i| i.meta_item()).any(|it| it.has_name(flag)) {
+ return true;
+ }
+ }
+ }
+
+ false
+ }
+
+ pub(crate) fn from_ast(attrs: &[ast::Attribute]) -> Attributes {
+ Attributes::from_ast_iter(attrs.iter().map(|attr| (attr, None)), false)
+ }
+
+ pub(crate) fn from_ast_with_additional(
+ attrs: &[ast::Attribute],
+ (additional_attrs, def_id): (&[ast::Attribute], DefId),
+ ) -> Attributes {
+ // Additional documentation should be shown before the original documentation.
+ let attrs1 = additional_attrs.iter().map(|attr| (attr, Some(def_id)));
+ let attrs2 = attrs.iter().map(|attr| (attr, None));
+ Attributes::from_ast_iter(attrs1.chain(attrs2), false)
+ }
+
+ pub(crate) fn from_ast_iter<'a>(
+ attrs: impl Iterator<Item = (&'a ast::Attribute, Option<DefId>)>,
+ doc_only: bool,
+ ) -> Attributes {
+ let mut doc_strings = Vec::new();
+ let mut other_attrs = Vec::new();
+ for (attr, parent_module) in attrs {
+ if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
+ trace!("got doc_str={doc_str:?}");
+ let doc = beautify_doc_string(doc_str, comment_kind);
+ let kind = if attr.is_doc_comment() {
+ DocFragmentKind::SugaredDoc
+ } else {
+ DocFragmentKind::RawDoc
+ };
+ let fragment = DocFragment { span: attr.span, doc, kind, parent_module, indent: 0 };
+ doc_strings.push(fragment);
+ } else if !doc_only {
+ other_attrs.push(attr.clone());
+ }
+ }
+
+ unindent_doc_fragments(&mut doc_strings);
+
+ Attributes { doc_strings, other_attrs }
+ }
+
+ /// Finds the `doc` attribute as a NameValue and returns the corresponding
+ /// value found.
+ pub(crate) fn doc_value(&self) -> Option<String> {
+ let mut iter = self.doc_strings.iter();
+
+ let ori = iter.next()?;
+ let mut out = String::new();
+ add_doc_fragment(&mut out, ori);
+ for new_frag in iter {
+ add_doc_fragment(&mut out, new_frag);
+ }
+ out.pop();
+ if out.is_empty() { None } else { Some(out) }
+ }
+
+ /// Return the doc-comments on this item, grouped by the module they came from.
+ /// The module can be different if this is a re-export with added documentation.
+ ///
+ /// The last newline is not trimmed so the produced strings are reusable between
+ /// early and late doc link resolution regardless of their position.
+ pub(crate) fn prepare_to_doc_link_resolution(&self) -> FxHashMap<Option<DefId>, String> {
+ let mut res = FxHashMap::default();
+ for fragment in &self.doc_strings {
+ let out_str = res.entry(fragment.parent_module).or_default();
+ add_doc_fragment(out_str, fragment);
+ }
+ res
+ }
+
+ /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
+ /// with newlines.
+ pub(crate) fn collapsed_doc_value(&self) -> Option<String> {
+ if self.doc_strings.is_empty() {
+ None
+ } else {
+ Some(collapse_doc_fragments(&self.doc_strings))
+ }
+ }
+
+ pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> {
+ let mut aliases = FxHashSet::default();
+
+ for attr in self.other_attrs.lists(sym::doc).filter(|a| a.has_name(sym::alias)) {
+ if let Some(values) = attr.meta_item_list() {
+ for l in values {
+ match l.literal().unwrap().kind {
+ ast::LitKind::Str(s, _) => {
+ aliases.insert(s);
+ }
+ _ => unreachable!(),
+ }
+ }
+ } else {
+ aliases.insert(attr.value_str().unwrap());
+ }
+ }
+ aliases.into_iter().collect::<Vec<_>>().into()
+ }
+}
+
+impl PartialEq for Attributes {
+ fn eq(&self, rhs: &Self) -> bool {
+ self.doc_strings == rhs.doc_strings
+ && self
+ .other_attrs
+ .iter()
+ .map(|attr| attr.id)
+ .eq(rhs.other_attrs.iter().map(|attr| attr.id))
+ }
+}
+
+impl Eq for Attributes {}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum GenericBound {
+ TraitBound(PolyTrait, hir::TraitBoundModifier),
+ Outlives(Lifetime),
+}
+
+impl GenericBound {
+ pub(crate) fn maybe_sized(cx: &mut DocContext<'_>) -> GenericBound {
+ let did = cx.tcx.require_lang_item(LangItem::Sized, None);
+ let empty = cx.tcx.intern_substs(&[]);
+ let path = external_path(cx, did, false, vec![], empty);
+ inline::record_extern_fqn(cx, did, ItemType::Trait);
+ GenericBound::TraitBound(
+ PolyTrait { trait_: path, generic_params: Vec::new() },
+ hir::TraitBoundModifier::Maybe,
+ )
+ }
+
+ pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool {
+ use rustc_hir::TraitBoundModifier as TBM;
+ if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, TBM::None) = *self {
+ if Some(trait_.def_id()) == cx.tcx.lang_items().sized_trait() {
+ return true;
+ }
+ }
+ false
+ }
+
+ pub(crate) fn get_poly_trait(&self) -> Option<PolyTrait> {
+ if let GenericBound::TraitBound(ref p, _) = *self {
+ return Some(p.clone());
+ }
+ None
+ }
+
+ pub(crate) fn get_trait_path(&self) -> Option<Path> {
+ if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, _) = *self {
+ Some(trait_.clone())
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct Lifetime(pub Symbol);
+
+impl Lifetime {
+ pub(crate) fn statik() -> Lifetime {
+ Lifetime(kw::StaticLifetime)
+ }
+
+ pub(crate) fn elided() -> Lifetime {
+ Lifetime(kw::UnderscoreLifetime)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum WherePredicate {
+ BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<Lifetime> },
+ RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> },
+ EqPredicate { lhs: Type, rhs: Term },
+}
+
+impl WherePredicate {
+ pub(crate) fn get_bounds(&self) -> Option<&[GenericBound]> {
+ match *self {
+ WherePredicate::BoundPredicate { ref bounds, .. } => Some(bounds),
+ WherePredicate::RegionPredicate { ref bounds, .. } => Some(bounds),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum GenericParamDefKind {
+ Lifetime { outlives: Vec<Lifetime> },
+ Type { did: DefId, bounds: Vec<GenericBound>, default: Option<Box<Type>>, synthetic: bool },
+ Const { did: DefId, ty: Box<Type>, default: Option<Box<String>> },
+}
+
+impl GenericParamDefKind {
+ pub(crate) fn is_type(&self) -> bool {
+ matches!(self, GenericParamDefKind::Type { .. })
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct GenericParamDef {
+ pub(crate) name: Symbol,
+ pub(crate) kind: GenericParamDefKind,
+}
+
+impl GenericParamDef {
+ pub(crate) fn is_synthetic_type_param(&self) -> bool {
+ match self.kind {
+ GenericParamDefKind::Lifetime { .. } | GenericParamDefKind::Const { .. } => false,
+ GenericParamDefKind::Type { synthetic, .. } => synthetic,
+ }
+ }
+
+ pub(crate) fn is_type(&self) -> bool {
+ self.kind.is_type()
+ }
+
+ pub(crate) fn get_bounds(&self) -> Option<&[GenericBound]> {
+ match self.kind {
+ GenericParamDefKind::Type { ref bounds, .. } => Some(bounds),
+ _ => None,
+ }
+ }
+}
+
+// maybe use a Generic enum and use Vec<Generic>?
+#[derive(Clone, Debug, Default)]
+pub(crate) struct Generics {
+ pub(crate) params: Vec<GenericParamDef>,
+ pub(crate) where_predicates: Vec<WherePredicate>,
+}
+
+impl Generics {
+ pub(crate) fn is_empty(&self) -> bool {
+ self.params.is_empty() && self.where_predicates.is_empty()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Function {
+ pub(crate) decl: FnDecl,
+ pub(crate) generics: Generics,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct FnDecl {
+ pub(crate) inputs: Arguments,
+ pub(crate) output: FnRetTy,
+ pub(crate) c_variadic: bool,
+}
+
+impl FnDecl {
+ pub(crate) fn self_type(&self) -> Option<SelfTy> {
+ self.inputs.values.get(0).and_then(|v| v.to_self())
+ }
+
+ /// Returns the sugared return type for an async function.
+ ///
+ /// For example, if the return type is `impl std::future::Future<Output = i32>`, this function
+ /// will return `i32`.
+ ///
+ /// # Panics
+ ///
+ /// This function will panic if the return type does not match the expected sugaring for async
+ /// functions.
+ pub(crate) fn sugared_async_return_type(&self) -> FnRetTy {
+ match &self.output {
+ FnRetTy::Return(Type::ImplTrait(bounds)) => match &bounds[0] {
+ GenericBound::TraitBound(PolyTrait { trait_, .. }, ..) => {
+ let bindings = trait_.bindings().unwrap();
+ let ret_ty = bindings[0].term();
+ let ty = ret_ty.ty().expect("Unexpected constant return term");
+ FnRetTy::Return(ty.clone())
+ }
+ _ => panic!("unexpected desugaring of async function"),
+ },
+ _ => panic!("unexpected desugaring of async function"),
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct Arguments {
+ pub(crate) values: Vec<Argument>,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct Argument {
+ pub(crate) type_: Type,
+ pub(crate) name: Symbol,
+ /// This field is used to represent "const" arguments from the `rustc_legacy_const_generics`
+ /// feature. More information in <https://github.com/rust-lang/rust/issues/83167>.
+ pub(crate) is_const: bool,
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub(crate) enum SelfTy {
+ SelfValue,
+ SelfBorrowed(Option<Lifetime>, Mutability),
+ SelfExplicit(Type),
+}
+
+impl Argument {
+ pub(crate) fn to_self(&self) -> Option<SelfTy> {
+ if self.name != kw::SelfLower {
+ return None;
+ }
+ if self.type_.is_self_type() {
+ return Some(SelfValue);
+ }
+ match self.type_ {
+ BorrowedRef { ref lifetime, mutability, ref type_ } if type_.is_self_type() => {
+ Some(SelfBorrowed(lifetime.clone(), mutability))
+ }
+ _ => Some(SelfExplicit(self.type_.clone())),
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum FnRetTy {
+ Return(Type),
+ DefaultReturn,
+}
+
+impl FnRetTy {
+ pub(crate) fn as_return(&self) -> Option<&Type> {
+ match self {
+ Return(ret) => Some(ret),
+ DefaultReturn => None,
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Trait {
+ pub(crate) def_id: DefId,
+ pub(crate) items: Vec<Item>,
+ pub(crate) generics: Generics,
+ pub(crate) bounds: Vec<GenericBound>,
+}
+
+impl Trait {
+ pub(crate) fn is_auto(&self, tcx: TyCtxt<'_>) -> bool {
+ tcx.trait_is_auto(self.def_id)
+ }
+ pub(crate) fn unsafety(&self, tcx: TyCtxt<'_>) -> hir::Unsafety {
+ tcx.trait_def(self.def_id).unsafety
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct TraitAlias {
+ pub(crate) generics: Generics,
+ pub(crate) bounds: Vec<GenericBound>,
+}
+
+/// A trait reference, which may have higher ranked lifetimes.
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct PolyTrait {
+ pub(crate) trait_: Path,
+ pub(crate) generic_params: Vec<GenericParamDef>,
+}
+
+/// Rustdoc's representation of types, mostly based on the [`hir::Ty`].
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum Type {
+ /// A named type, which could be a trait.
+ ///
+ /// This is mostly Rustdoc's version of [`hir::Path`].
+ /// It has to be different because Rustdoc's [`PathSegment`] can contain cleaned generics.
+ Path { path: Path },
+ /// A `dyn Trait` object: `dyn for<'a> Trait<'a> + Send + 'static`
+ DynTrait(Vec<PolyTrait>, Option<Lifetime>),
+ /// A type parameter.
+ Generic(Symbol),
+ /// A primitive (aka, builtin) type.
+ Primitive(PrimitiveType),
+ /// A function pointer: `extern "ABI" fn(...) -> ...`
+ BareFunction(Box<BareFunctionDecl>),
+ /// A tuple type: `(i32, &str)`.
+ Tuple(Vec<Type>),
+ /// A slice type (does *not* include the `&`): `[i32]`
+ Slice(Box<Type>),
+ /// An array type.
+ ///
+ /// The `String` field is a stringified version of the array's length parameter.
+ Array(Box<Type>, String),
+ /// A raw pointer type: `*const i32`, `*mut i32`
+ RawPointer(Mutability, Box<Type>),
+ /// A reference type: `&i32`, `&'a mut Foo`
+ BorrowedRef { lifetime: Option<Lifetime>, mutability: Mutability, type_: Box<Type> },
+
+ /// A qualified path to an associated item: `<Type as Trait>::Name`
+ QPath {
+ assoc: Box<PathSegment>,
+ self_type: Box<Type>,
+ /// FIXME: compute this field on demand.
+ should_show_cast: bool,
+ trait_: Path,
+ },
+
+ /// A type that is inferred: `_`
+ Infer,
+
+ /// An `impl Trait`: `impl TraitA + TraitB + ...`
+ ImplTrait(Vec<GenericBound>),
+}
+
+impl Type {
+ /// When comparing types for equality, it can help to ignore `&` wrapping.
+ pub(crate) fn without_borrowed_ref(&self) -> &Type {
+ let mut result = self;
+ while let Type::BorrowedRef { type_, .. } = result {
+ result = &*type_;
+ }
+ result
+ }
+
+ /// Check if two types are "potentially the same".
+ /// This is different from `Eq`, because it knows that things like
+ /// `Placeholder` are possible matches for everything.
+ pub(crate) fn is_same(&self, other: &Self, cache: &Cache) -> bool {
+ match (self, other) {
+ // Recursive cases.
+ (Type::Tuple(a), Type::Tuple(b)) => {
+ a.len() == b.len() && a.iter().zip(b).all(|(a, b)| a.is_same(b, cache))
+ }
+ (Type::Slice(a), Type::Slice(b)) => a.is_same(b, cache),
+ (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_same(b, cache),
+ (Type::RawPointer(mutability, type_), Type::RawPointer(b_mutability, b_type_)) => {
+ mutability == b_mutability && type_.is_same(b_type_, cache)
+ }
+ (
+ Type::BorrowedRef { mutability, type_, .. },
+ Type::BorrowedRef { mutability: b_mutability, type_: b_type_, .. },
+ ) => mutability == b_mutability && type_.is_same(b_type_, cache),
+ // Placeholders and generics are equal to all other types.
+ (Type::Infer, _) | (_, Type::Infer) => true,
+ (Type::Generic(_), _) | (_, Type::Generic(_)) => true,
+ // Other cases, such as primitives, just use recursion.
+ (a, b) => a
+ .def_id(cache)
+ .and_then(|a| Some((a, b.def_id(cache)?)))
+ .map(|(a, b)| a == b)
+ .unwrap_or(false),
+ }
+ }
+
+ pub(crate) fn primitive_type(&self) -> Option<PrimitiveType> {
+ match *self {
+ Primitive(p) | BorrowedRef { type_: box Primitive(p), .. } => Some(p),
+ Slice(..) | BorrowedRef { type_: box Slice(..), .. } => Some(PrimitiveType::Slice),
+ Array(..) | BorrowedRef { type_: box Array(..), .. } => Some(PrimitiveType::Array),
+ Tuple(ref tys) => {
+ if tys.is_empty() {
+ Some(PrimitiveType::Unit)
+ } else {
+ Some(PrimitiveType::Tuple)
+ }
+ }
+ RawPointer(..) => Some(PrimitiveType::RawPointer),
+ BareFunction(..) => Some(PrimitiveType::Fn),
+ _ => None,
+ }
+ }
+
+ /// Checks if this is a `T::Name` path for an associated type.
+ pub(crate) fn is_assoc_ty(&self) -> bool {
+ match self {
+ Type::Path { path, .. } => path.is_assoc_ty(),
+ _ => false,
+ }
+ }
+
+ pub(crate) fn is_self_type(&self) -> bool {
+ match *self {
+ Generic(name) => name == kw::SelfUpper,
+ _ => false,
+ }
+ }
+
+ pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
+ match self {
+ Type::Path { path, .. } => path.generics(),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn is_full_generic(&self) -> bool {
+ matches!(self, Type::Generic(_))
+ }
+
+ pub(crate) fn is_impl_trait(&self) -> bool {
+ matches!(self, Type::ImplTrait(_))
+ }
+
+ pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
+ if let QPath { self_type, trait_, assoc, .. } = self {
+ Some((self_type, trait_.def_id(), *assoc.clone()))
+ } else {
+ None
+ }
+ }
+
+ fn inner_def_id(&self, cache: Option<&Cache>) -> Option<DefId> {
+ let t: PrimitiveType = match *self {
+ Type::Path { ref path } => return Some(path.def_id()),
+ DynTrait(ref bounds, _) => return Some(bounds[0].trait_.def_id()),
+ Primitive(p) => return cache.and_then(|c| c.primitive_locations.get(&p).cloned()),
+ BorrowedRef { type_: box Generic(..), .. } => PrimitiveType::Reference,
+ BorrowedRef { ref type_, .. } => return type_.inner_def_id(cache),
+ Tuple(ref tys) => {
+ if tys.is_empty() {
+ PrimitiveType::Unit
+ } else {
+ PrimitiveType::Tuple
+ }
+ }
+ BareFunction(..) => PrimitiveType::Fn,
+ Slice(..) => PrimitiveType::Slice,
+ Array(..) => PrimitiveType::Array,
+ RawPointer(..) => PrimitiveType::RawPointer,
+ QPath { ref self_type, .. } => return self_type.inner_def_id(cache),
+ Generic(_) | Infer | ImplTrait(_) => return None,
+ };
+ cache.and_then(|c| Primitive(t).def_id(c))
+ }
+
+ /// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s.
+ ///
+ /// [clean]: crate::clean
+ pub(crate) fn def_id(&self, cache: &Cache) -> Option<DefId> {
+ self.inner_def_id(Some(cache))
+ }
+}
+
+/// A primitive (aka, builtin) type.
+///
+/// This represents things like `i32`, `str`, etc.
+///
+/// N.B. This has to be different from [`hir::PrimTy`] because it also includes types that aren't
+/// paths, like [`Self::Unit`].
+#[derive(Clone, PartialEq, Eq, Hash, Copy, Debug)]
+pub(crate) enum PrimitiveType {
+ Isize,
+ I8,
+ I16,
+ I32,
+ I64,
+ I128,
+ Usize,
+ U8,
+ U16,
+ U32,
+ U64,
+ U128,
+ F32,
+ F64,
+ Char,
+ Bool,
+ Str,
+ Slice,
+ Array,
+ Tuple,
+ Unit,
+ RawPointer,
+ Reference,
+ Fn,
+ Never,
+}
+
+type SimplifiedTypes = FxHashMap<PrimitiveType, ArrayVec<SimplifiedType, 3>>;
+impl PrimitiveType {
+ pub(crate) fn from_hir(prim: hir::PrimTy) -> PrimitiveType {
+ use ast::{FloatTy, IntTy, UintTy};
+ match prim {
+ hir::PrimTy::Int(IntTy::Isize) => PrimitiveType::Isize,
+ hir::PrimTy::Int(IntTy::I8) => PrimitiveType::I8,
+ hir::PrimTy::Int(IntTy::I16) => PrimitiveType::I16,
+ hir::PrimTy::Int(IntTy::I32) => PrimitiveType::I32,
+ hir::PrimTy::Int(IntTy::I64) => PrimitiveType::I64,
+ hir::PrimTy::Int(IntTy::I128) => PrimitiveType::I128,
+ hir::PrimTy::Uint(UintTy::Usize) => PrimitiveType::Usize,
+ hir::PrimTy::Uint(UintTy::U8) => PrimitiveType::U8,
+ hir::PrimTy::Uint(UintTy::U16) => PrimitiveType::U16,
+ hir::PrimTy::Uint(UintTy::U32) => PrimitiveType::U32,
+ hir::PrimTy::Uint(UintTy::U64) => PrimitiveType::U64,
+ hir::PrimTy::Uint(UintTy::U128) => PrimitiveType::U128,
+ hir::PrimTy::Float(FloatTy::F32) => PrimitiveType::F32,
+ hir::PrimTy::Float(FloatTy::F64) => PrimitiveType::F64,
+ hir::PrimTy::Str => PrimitiveType::Str,
+ hir::PrimTy::Bool => PrimitiveType::Bool,
+ hir::PrimTy::Char => PrimitiveType::Char,
+ }
+ }
+
+ pub(crate) fn from_symbol(s: Symbol) -> Option<PrimitiveType> {
+ match s {
+ sym::isize => Some(PrimitiveType::Isize),
+ sym::i8 => Some(PrimitiveType::I8),
+ sym::i16 => Some(PrimitiveType::I16),
+ sym::i32 => Some(PrimitiveType::I32),
+ sym::i64 => Some(PrimitiveType::I64),
+ sym::i128 => Some(PrimitiveType::I128),
+ sym::usize => Some(PrimitiveType::Usize),
+ sym::u8 => Some(PrimitiveType::U8),
+ sym::u16 => Some(PrimitiveType::U16),
+ sym::u32 => Some(PrimitiveType::U32),
+ sym::u64 => Some(PrimitiveType::U64),
+ sym::u128 => Some(PrimitiveType::U128),
+ sym::bool => Some(PrimitiveType::Bool),
+ sym::char => Some(PrimitiveType::Char),
+ sym::str => Some(PrimitiveType::Str),
+ sym::f32 => Some(PrimitiveType::F32),
+ sym::f64 => Some(PrimitiveType::F64),
+ sym::array => Some(PrimitiveType::Array),
+ sym::slice => Some(PrimitiveType::Slice),
+ sym::tuple => Some(PrimitiveType::Tuple),
+ sym::unit => Some(PrimitiveType::Unit),
+ sym::pointer => Some(PrimitiveType::RawPointer),
+ sym::reference => Some(PrimitiveType::Reference),
+ kw::Fn => Some(PrimitiveType::Fn),
+ sym::never => Some(PrimitiveType::Never),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn simplified_types() -> &'static SimplifiedTypes {
+ use ty::fast_reject::SimplifiedTypeGen::*;
+ use ty::{FloatTy, IntTy, UintTy};
+ use PrimitiveType::*;
+ static CELL: OnceCell<SimplifiedTypes> = OnceCell::new();
+
+ let single = |x| iter::once(x).collect();
+ CELL.get_or_init(move || {
+ map! {
+ Isize => single(IntSimplifiedType(IntTy::Isize)),
+ I8 => single(IntSimplifiedType(IntTy::I8)),
+ I16 => single(IntSimplifiedType(IntTy::I16)),
+ I32 => single(IntSimplifiedType(IntTy::I32)),
+ I64 => single(IntSimplifiedType(IntTy::I64)),
+ I128 => single(IntSimplifiedType(IntTy::I128)),
+ Usize => single(UintSimplifiedType(UintTy::Usize)),
+ U8 => single(UintSimplifiedType(UintTy::U8)),
+ U16 => single(UintSimplifiedType(UintTy::U16)),
+ U32 => single(UintSimplifiedType(UintTy::U32)),
+ U64 => single(UintSimplifiedType(UintTy::U64)),
+ U128 => single(UintSimplifiedType(UintTy::U128)),
+ F32 => single(FloatSimplifiedType(FloatTy::F32)),
+ F64 => single(FloatSimplifiedType(FloatTy::F64)),
+ Str => single(StrSimplifiedType),
+ Bool => single(BoolSimplifiedType),
+ Char => single(CharSimplifiedType),
+ Array => single(ArraySimplifiedType),
+ Slice => single(SliceSimplifiedType),
+ // FIXME: If we ever add an inherent impl for tuples
+ // with different lengths, they won't show in rustdoc.
+ //
+ // Either manually update this arrayvec at this point
+ // or start with a more complex refactoring.
+ Tuple => [TupleSimplifiedType(1), TupleSimplifiedType(2), TupleSimplifiedType(3)].into(),
+ Unit => single(TupleSimplifiedType(0)),
+ RawPointer => [PtrSimplifiedType(Mutability::Not), PtrSimplifiedType(Mutability::Mut)].into_iter().collect(),
+ Reference => [RefSimplifiedType(Mutability::Not), RefSimplifiedType(Mutability::Mut)].into_iter().collect(),
+ // FIXME: This will be wrong if we ever add inherent impls
+ // for function pointers.
+ Fn => single(FunctionSimplifiedType(1)),
+ Never => single(NeverSimplifiedType),
+ }
+ })
+ }
+
+ pub(crate) fn impls<'tcx>(&self, tcx: TyCtxt<'tcx>) -> impl Iterator<Item = DefId> + 'tcx {
+ Self::simplified_types()
+ .get(self)
+ .into_iter()
+ .flatten()
+ .flat_map(move |&simp| tcx.incoherent_impls(simp))
+ .copied()
+ }
+
+ pub(crate) fn all_impls(tcx: TyCtxt<'_>) -> impl Iterator<Item = DefId> + '_ {
+ Self::simplified_types()
+ .values()
+ .flatten()
+ .flat_map(move |&simp| tcx.incoherent_impls(simp))
+ .copied()
+ }
+
+ pub(crate) fn as_sym(&self) -> Symbol {
+ use PrimitiveType::*;
+ match self {
+ Isize => sym::isize,
+ I8 => sym::i8,
+ I16 => sym::i16,
+ I32 => sym::i32,
+ I64 => sym::i64,
+ I128 => sym::i128,
+ Usize => sym::usize,
+ U8 => sym::u8,
+ U16 => sym::u16,
+ U32 => sym::u32,
+ U64 => sym::u64,
+ U128 => sym::u128,
+ F32 => sym::f32,
+ F64 => sym::f64,
+ Str => sym::str,
+ Bool => sym::bool,
+ Char => sym::char,
+ Array => sym::array,
+ Slice => sym::slice,
+ Tuple => sym::tuple,
+ Unit => sym::unit,
+ RawPointer => sym::pointer,
+ Reference => sym::reference,
+ Fn => kw::Fn,
+ Never => sym::never,
+ }
+ }
+
+ /// Returns the DefId of the module with `doc(primitive)` for this primitive type.
+ /// Panics if there is no such module.
+ ///
+ /// This gives precedence to primitives defined in the current crate, and deprioritizes primitives defined in `core`,
+ /// but otherwise, if multiple crates define the same primitive, there is no guarantee of which will be picked.
+ /// In particular, if a crate depends on both `std` and another crate that also defines `doc(primitive)`, then
+ /// it's entirely random whether `std` or the other crate is picked. (no_std crates are usually fine unless multiple dependencies define a primitive.)
+ pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> {
+ static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new();
+ PRIMITIVE_LOCATIONS.get_or_init(|| {
+ let mut primitive_locations = FxHashMap::default();
+ // NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate.
+ // This is a degenerate case that I don't plan to support.
+ for &crate_num in tcx.crates(()) {
+ let e = ExternalCrate { crate_num };
+ let crate_name = e.name(tcx);
+ debug!(?crate_num, ?crate_name);
+ for &(def_id, prim) in &e.primitives(tcx) {
+ // HACK: try to link to std instead where possible
+ if crate_name == sym::core && primitive_locations.contains_key(&prim) {
+ continue;
+ }
+ primitive_locations.insert(prim, def_id);
+ }
+ }
+ let local_primitives = ExternalCrate { crate_num: LOCAL_CRATE }.primitives(tcx);
+ for (def_id, prim) in local_primitives {
+ primitive_locations.insert(prim, def_id);
+ }
+ primitive_locations
+ })
+ }
+}
+
+impl From<ast::IntTy> for PrimitiveType {
+ fn from(int_ty: ast::IntTy) -> PrimitiveType {
+ match int_ty {
+ ast::IntTy::Isize => PrimitiveType::Isize,
+ ast::IntTy::I8 => PrimitiveType::I8,
+ ast::IntTy::I16 => PrimitiveType::I16,
+ ast::IntTy::I32 => PrimitiveType::I32,
+ ast::IntTy::I64 => PrimitiveType::I64,
+ ast::IntTy::I128 => PrimitiveType::I128,
+ }
+ }
+}
+
+impl From<ast::UintTy> for PrimitiveType {
+ fn from(uint_ty: ast::UintTy) -> PrimitiveType {
+ match uint_ty {
+ ast::UintTy::Usize => PrimitiveType::Usize,
+ ast::UintTy::U8 => PrimitiveType::U8,
+ ast::UintTy::U16 => PrimitiveType::U16,
+ ast::UintTy::U32 => PrimitiveType::U32,
+ ast::UintTy::U64 => PrimitiveType::U64,
+ ast::UintTy::U128 => PrimitiveType::U128,
+ }
+ }
+}
+
+impl From<ast::FloatTy> for PrimitiveType {
+ fn from(float_ty: ast::FloatTy) -> PrimitiveType {
+ match float_ty {
+ ast::FloatTy::F32 => PrimitiveType::F32,
+ ast::FloatTy::F64 => PrimitiveType::F64,
+ }
+ }
+}
+
+impl From<ty::IntTy> for PrimitiveType {
+ fn from(int_ty: ty::IntTy) -> PrimitiveType {
+ match int_ty {
+ ty::IntTy::Isize => PrimitiveType::Isize,
+ ty::IntTy::I8 => PrimitiveType::I8,
+ ty::IntTy::I16 => PrimitiveType::I16,
+ ty::IntTy::I32 => PrimitiveType::I32,
+ ty::IntTy::I64 => PrimitiveType::I64,
+ ty::IntTy::I128 => PrimitiveType::I128,
+ }
+ }
+}
+
+impl From<ty::UintTy> for PrimitiveType {
+ fn from(uint_ty: ty::UintTy) -> PrimitiveType {
+ match uint_ty {
+ ty::UintTy::Usize => PrimitiveType::Usize,
+ ty::UintTy::U8 => PrimitiveType::U8,
+ ty::UintTy::U16 => PrimitiveType::U16,
+ ty::UintTy::U32 => PrimitiveType::U32,
+ ty::UintTy::U64 => PrimitiveType::U64,
+ ty::UintTy::U128 => PrimitiveType::U128,
+ }
+ }
+}
+
+impl From<ty::FloatTy> for PrimitiveType {
+ fn from(float_ty: ty::FloatTy) -> PrimitiveType {
+ match float_ty {
+ ty::FloatTy::F32 => PrimitiveType::F32,
+ ty::FloatTy::F64 => PrimitiveType::F64,
+ }
+ }
+}
+
+impl From<hir::PrimTy> for PrimitiveType {
+ fn from(prim_ty: hir::PrimTy) -> PrimitiveType {
+ match prim_ty {
+ hir::PrimTy::Int(int_ty) => int_ty.into(),
+ hir::PrimTy::Uint(uint_ty) => uint_ty.into(),
+ hir::PrimTy::Float(float_ty) => float_ty.into(),
+ hir::PrimTy::Str => PrimitiveType::Str,
+ hir::PrimTy::Bool => PrimitiveType::Bool,
+ hir::PrimTy::Char => PrimitiveType::Char,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub(crate) enum Visibility {
+ /// `pub`
+ Public,
+ /// Visibility inherited from parent.
+ ///
+ /// For example, this is the visibility of private items and of enum variants.
+ Inherited,
+ /// `pub(crate)`, `pub(super)`, or `pub(in path::to::somewhere)`
+ Restricted(DefId),
+}
+
+impl Visibility {
+ pub(crate) fn is_public(&self) -> bool {
+ matches!(self, Visibility::Public)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Struct {
+ pub(crate) struct_type: CtorKind,
+ pub(crate) generics: Generics,
+ pub(crate) fields: Vec<Item>,
+}
+
+impl Struct {
+ pub(crate) fn has_stripped_entries(&self) -> bool {
+ self.fields.iter().any(|f| f.is_stripped())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Union {
+ pub(crate) generics: Generics,
+ pub(crate) fields: Vec<Item>,
+}
+
+impl Union {
+ pub(crate) fn has_stripped_entries(&self) -> bool {
+ self.fields.iter().any(|f| f.is_stripped())
+ }
+}
+
+/// This is a more limited form of the standard Struct, different in that
+/// it lacks the things most items have (name, id, parameterization). Found
+/// only as a variant in an enum.
+#[derive(Clone, Debug)]
+pub(crate) struct VariantStruct {
+ pub(crate) struct_type: CtorKind,
+ pub(crate) fields: Vec<Item>,
+}
+
+impl VariantStruct {
+ pub(crate) fn has_stripped_entries(&self) -> bool {
+ self.fields.iter().any(|f| f.is_stripped())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Enum {
+ pub(crate) variants: IndexVec<VariantIdx, Item>,
+ pub(crate) generics: Generics,
+}
+
+impl Enum {
+ pub(crate) fn has_stripped_entries(&self) -> bool {
+ self.variants.iter().any(|f| f.is_stripped())
+ }
+
+ pub(crate) fn variants(&self) -> impl Iterator<Item = &Item> {
+ self.variants.iter().filter(|v| !v.is_stripped())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum Variant {
+ CLike,
+ Tuple(Vec<Item>),
+ Struct(VariantStruct),
+}
+
+impl Variant {
+ pub(crate) fn has_stripped_entries(&self) -> Option<bool> {
+ match *self {
+ Self::Struct(ref struct_) => Some(struct_.has_stripped_entries()),
+ Self::CLike | Self::Tuple(_) => None,
+ }
+ }
+}
+
+/// Small wrapper around [`rustc_span::Span`] that adds helper methods
+/// and enforces calling [`rustc_span::Span::source_callsite()`].
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct Span(rustc_span::Span);
+
+impl Span {
+ /// Wraps a [`rustc_span::Span`]. In case this span is the result of a macro expansion, the
+ /// span will be updated to point to the macro invocation instead of the macro definition.
+ ///
+ /// (See rust-lang/rust#39726)
+ pub(crate) fn new(sp: rustc_span::Span) -> Self {
+ Self(sp.source_callsite())
+ }
+
+ pub(crate) fn inner(&self) -> rustc_span::Span {
+ self.0
+ }
+
+ pub(crate) fn dummy() -> Self {
+ Self(rustc_span::DUMMY_SP)
+ }
+
+ pub(crate) fn is_dummy(&self) -> bool {
+ self.0.is_dummy()
+ }
+
+ pub(crate) fn filename(&self, sess: &Session) -> FileName {
+ sess.source_map().span_to_filename(self.0)
+ }
+
+ pub(crate) fn lo(&self, sess: &Session) -> Loc {
+ sess.source_map().lookup_char_pos(self.0.lo())
+ }
+
+ pub(crate) fn hi(&self, sess: &Session) -> Loc {
+ sess.source_map().lookup_char_pos(self.0.hi())
+ }
+
+ pub(crate) fn cnum(&self, sess: &Session) -> CrateNum {
+ // FIXME: is there a time when the lo and hi crate would be different?
+ self.lo(sess).file.cnum
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct Path {
+ pub(crate) res: Res,
+ pub(crate) segments: Vec<PathSegment>,
+}
+
+impl Path {
+ pub(crate) fn def_id(&self) -> DefId {
+ self.res.def_id()
+ }
+
+ pub(crate) fn last_opt(&self) -> Option<Symbol> {
+ self.segments.last().map(|s| s.name)
+ }
+
+ pub(crate) fn last(&self) -> Symbol {
+ self.last_opt().expect("segments were empty")
+ }
+
+ pub(crate) fn whole_name(&self) -> String {
+ self.segments
+ .iter()
+ .map(|s| if s.name == kw::PathRoot { "" } else { s.name.as_str() })
+ .intersperse("::")
+ .collect()
+ }
+
+ /// Checks if this is a `T::Name` path for an associated type.
+ pub(crate) fn is_assoc_ty(&self) -> bool {
+ match self.res {
+ Res::SelfTy { .. } if self.segments.len() != 1 => true,
+ Res::Def(DefKind::TyParam, _) if self.segments.len() != 1 => true,
+ Res::Def(DefKind::AssocTy, _) => true,
+ _ => false,
+ }
+ }
+
+ pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
+ self.segments.last().and_then(|seg| {
+ if let GenericArgs::AngleBracketed { ref args, .. } = seg.args {
+ Some(
+ args.iter()
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ .collect(),
+ )
+ } else {
+ None
+ }
+ })
+ }
+
+ pub(crate) fn bindings(&self) -> Option<&[TypeBinding]> {
+ self.segments.last().and_then(|seg| {
+ if let GenericArgs::AngleBracketed { ref bindings, .. } = seg.args {
+ Some(&**bindings)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum GenericArg {
+ Lifetime(Lifetime),
+ Type(Type),
+ Const(Box<Constant>),
+ Infer,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum GenericArgs {
+ AngleBracketed { args: Box<[GenericArg]>, bindings: ThinVec<TypeBinding> },
+ Parenthesized { inputs: Box<[Type]>, output: Option<Box<Type>> },
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct PathSegment {
+ pub(crate) name: Symbol,
+ pub(crate) args: GenericArgs,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Typedef {
+ pub(crate) type_: Type,
+ pub(crate) generics: Generics,
+ /// `type_` can come from either the HIR or from metadata. If it comes from HIR, it may be a type
+ /// alias instead of the final type. This will always have the final type, regardless of whether
+ /// `type_` came from HIR or from metadata.
+ ///
+ /// If `item_type.is_none()`, `type_` is guaranteed to come from metadata (and therefore hold the
+ /// final type).
+ pub(crate) item_type: Option<Type>,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct OpaqueTy {
+ pub(crate) bounds: Vec<GenericBound>,
+ pub(crate) generics: Generics,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct BareFunctionDecl {
+ pub(crate) unsafety: hir::Unsafety,
+ pub(crate) generic_params: Vec<GenericParamDef>,
+ pub(crate) decl: FnDecl,
+ pub(crate) abi: Abi,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Static {
+ pub(crate) type_: Type,
+ pub(crate) mutability: Mutability,
+ pub(crate) expr: Option<BodyId>,
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub(crate) struct Constant {
+ pub(crate) type_: Type,
+ pub(crate) kind: ConstantKind,
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub(crate) enum Term {
+ Type(Type),
+ Constant(Constant),
+}
+
+impl Term {
+ pub(crate) fn ty(&self) -> Option<&Type> {
+ if let Term::Type(ty) = self { Some(ty) } else { None }
+ }
+}
+
+impl From<Type> for Term {
+ fn from(ty: Type) -> Self {
+ Term::Type(ty)
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub(crate) enum ConstantKind {
+ /// This is the wrapper around `ty::Const` for a non-local constant. Because it doesn't have a
+ /// `BodyId`, we need to handle it on its own.
+ ///
+ /// Note that `ty::Const` includes generic parameters, and may not always be uniquely identified
+ /// by a DefId. So this field must be different from `Extern`.
+ TyConst { expr: String },
+ /// A constant (expression) that's not an item or associated item. These are usually found
+ /// nested inside types (e.g., array lengths) or expressions (e.g., repeat counts), and also
+ /// used to define explicit discriminant values for enum variants.
+ Anonymous { body: BodyId },
+ /// A constant from a different crate.
+ Extern { def_id: DefId },
+ /// `const FOO: u32 = ...;`
+ Local { def_id: DefId, body: BodyId },
+}
+
+impl Constant {
+ pub(crate) fn expr(&self, tcx: TyCtxt<'_>) -> String {
+ self.kind.expr(tcx)
+ }
+
+ pub(crate) fn value(&self, tcx: TyCtxt<'_>) -> Option<String> {
+ self.kind.value(tcx)
+ }
+
+ pub(crate) fn is_literal(&self, tcx: TyCtxt<'_>) -> bool {
+ self.kind.is_literal(tcx)
+ }
+}
+
+impl ConstantKind {
+ pub(crate) fn expr(&self, tcx: TyCtxt<'_>) -> String {
+ match *self {
+ ConstantKind::TyConst { ref expr } => expr.clone(),
+ ConstantKind::Extern { def_id } => print_inlined_const(tcx, def_id),
+ ConstantKind::Local { body, .. } | ConstantKind::Anonymous { body } => {
+ print_const_expr(tcx, body)
+ }
+ }
+ }
+
+ pub(crate) fn value(&self, tcx: TyCtxt<'_>) -> Option<String> {
+ match *self {
+ ConstantKind::TyConst { .. } | ConstantKind::Anonymous { .. } => None,
+ ConstantKind::Extern { def_id } | ConstantKind::Local { def_id, .. } => {
+ print_evaluated_const(tcx, def_id)
+ }
+ }
+ }
+
+ pub(crate) fn is_literal(&self, tcx: TyCtxt<'_>) -> bool {
+ match *self {
+ ConstantKind::TyConst { .. } => false,
+ ConstantKind::Extern { def_id } => def_id.as_local().map_or(false, |def_id| {
+ is_literal_expr(tcx, tcx.hir().local_def_id_to_hir_id(def_id))
+ }),
+ ConstantKind::Local { body, .. } | ConstantKind::Anonymous { body } => {
+ is_literal_expr(tcx, body.hir_id)
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Impl {
+ pub(crate) unsafety: hir::Unsafety,
+ pub(crate) generics: Generics,
+ pub(crate) trait_: Option<Path>,
+ pub(crate) for_: Type,
+ pub(crate) items: Vec<Item>,
+ pub(crate) polarity: ty::ImplPolarity,
+ pub(crate) kind: ImplKind,
+}
+
+impl Impl {
+ pub(crate) fn provided_trait_methods(&self, tcx: TyCtxt<'_>) -> FxHashSet<Symbol> {
+ self.trait_
+ .as_ref()
+ .map(|t| t.def_id())
+ .map(|did| tcx.provided_trait_methods(did).map(|meth| meth.name).collect())
+ .unwrap_or_default()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum ImplKind {
+ Normal,
+ Auto,
+ FakeVaradic,
+ Blanket(Box<Type>),
+}
+
+impl ImplKind {
+ pub(crate) fn is_auto(&self) -> bool {
+ matches!(self, ImplKind::Auto)
+ }
+
+ pub(crate) fn is_blanket(&self) -> bool {
+ matches!(self, ImplKind::Blanket(_))
+ }
+
+ pub(crate) fn is_fake_variadic(&self) -> bool {
+ matches!(self, ImplKind::FakeVaradic)
+ }
+
+ pub(crate) fn as_blanket_ty(&self) -> Option<&Type> {
+ match self {
+ ImplKind::Blanket(ty) => Some(ty),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Import {
+ pub(crate) kind: ImportKind,
+ pub(crate) source: ImportSource,
+ pub(crate) should_be_displayed: bool,
+}
+
+impl Import {
+ pub(crate) fn new_simple(
+ name: Symbol,
+ source: ImportSource,
+ should_be_displayed: bool,
+ ) -> Self {
+ Self { kind: ImportKind::Simple(name), source, should_be_displayed }
+ }
+
+ pub(crate) fn new_glob(source: ImportSource, should_be_displayed: bool) -> Self {
+ Self { kind: ImportKind::Glob, source, should_be_displayed }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum ImportKind {
+ // use source as str;
+ Simple(Symbol),
+ // use source::*;
+ Glob,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct ImportSource {
+ pub(crate) path: Path,
+ pub(crate) did: Option<DefId>,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Macro {
+ pub(crate) source: String,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct ProcMacro {
+ pub(crate) kind: MacroKind,
+ pub(crate) helpers: Vec<Symbol>,
+}
+
+/// An type binding on an associated type (e.g., `A = Bar` in `Foo<A = Bar>` or
+/// `A: Send + Sync` in `Foo<A: Send + Sync>`).
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct TypeBinding {
+ pub(crate) assoc: PathSegment,
+ pub(crate) kind: TypeBindingKind,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) enum TypeBindingKind {
+ Equality { term: Term },
+ Constraint { bounds: Vec<GenericBound> },
+}
+
+impl TypeBinding {
+ pub(crate) fn term(&self) -> &Term {
+ match self.kind {
+ TypeBindingKind::Equality { ref term } => term,
+ _ => panic!("expected equality type binding for parenthesized generic args"),
+ }
+ }
+}
+
+/// The type, lifetime, or constant that a private type alias's parameter should be
+/// replaced with when expanding a use of that type alias.
+///
+/// For example:
+///
+/// ```
+/// type PrivAlias<T> = Vec<T>;
+///
+/// pub fn public_fn() -> PrivAlias<i32> { vec![] }
+/// ```
+///
+/// `public_fn`'s docs will show it as returning `Vec<i32>`, since `PrivAlias` is private.
+/// [`SubstParam`] is used to record that `T` should be mapped to `i32`.
+pub(crate) enum SubstParam {
+ Type(Type),
+ Lifetime(Lifetime),
+ Constant(Constant),
+}
+
+impl SubstParam {
+ pub(crate) fn as_ty(&self) -> Option<&Type> {
+ if let Self::Type(ty) = self { Some(ty) } else { None }
+ }
+
+ pub(crate) fn as_lt(&self) -> Option<&Lifetime> {
+ if let Self::Lifetime(lt) = self { Some(lt) } else { None }
+ }
+}
+
+// Some nodes are used a lot. Make sure they don't unintentionally get bigger.
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+mod size_asserts {
+ use super::*;
+ // These are in alphabetical order, which is easy to maintain.
+ rustc_data_structures::static_assert_size!(Crate, 72); // frequently moved by-value
+ rustc_data_structures::static_assert_size!(DocFragment, 32);
+ rustc_data_structures::static_assert_size!(GenericArg, 80);
+ rustc_data_structures::static_assert_size!(GenericArgs, 32);
+ rustc_data_structures::static_assert_size!(GenericParamDef, 56);
+ rustc_data_structures::static_assert_size!(Item, 56);
+ rustc_data_structures::static_assert_size!(ItemKind, 112);
+ rustc_data_structures::static_assert_size!(PathSegment, 40);
+ rustc_data_structures::static_assert_size!(Type, 72);
+}
diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs
new file mode 100644
index 000000000..71eddf434
--- /dev/null
+++ b/src/librustdoc/clean/types/tests.rs
@@ -0,0 +1,70 @@
+use super::*;
+
+use crate::clean::collapse_doc_fragments;
+
+use rustc_span::create_default_session_globals_then;
+use rustc_span::source_map::DUMMY_SP;
+use rustc_span::symbol::Symbol;
+
+fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
+ vec![DocFragment {
+ span: DUMMY_SP,
+ parent_module: None,
+ doc: Symbol::intern(s),
+ kind: DocFragmentKind::SugaredDoc,
+ indent: 0,
+ }]
+}
+
+#[track_caller]
+fn run_test(input: &str, expected: &str) {
+ create_default_session_globals_then(|| {
+ let mut s = create_doc_fragment(input);
+ unindent_doc_fragments(&mut s);
+ assert_eq!(collapse_doc_fragments(&s), expected);
+ });
+}
+
+#[test]
+fn should_unindent() {
+ run_test(" line1\n line2", "line1\nline2");
+}
+
+#[test]
+fn should_unindent_multiple_paragraphs() {
+ run_test(" line1\n\n line2", "line1\n\nline2");
+}
+
+#[test]
+fn should_leave_multiple_indent_levels() {
+ // Line 2 is indented another level beyond the
+ // base indentation and should be preserved
+ run_test(" line1\n\n line2", "line1\n\n line2");
+}
+
+#[test]
+fn should_ignore_first_line_indent() {
+ run_test("line1\n line2", "line1\n line2");
+}
+
+#[test]
+fn should_not_ignore_first_line_indent_in_a_single_line_para() {
+ run_test("line1\n\n line2", "line1\n\n line2");
+}
+
+#[test]
+fn should_unindent_tabs() {
+ run_test("\tline1\n\tline2", "line1\nline2");
+}
+
+#[test]
+fn should_trim_mixed_indentation() {
+ run_test("\t line1\n\t line2", "line1\nline2");
+ run_test(" \tline1\n \tline2", "line1\nline2");
+}
+
+#[test]
+fn should_not_trim() {
+ run_test("\t line1 \n\t line2", "line1 \nline2");
+ run_test(" \tline1 \n \tline2", "line1 \nline2");
+}
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
new file mode 100644
index 000000000..43e71e90a
--- /dev/null
+++ b/src/librustdoc/clean/utils.rs
@@ -0,0 +1,616 @@
+use crate::clean::auto_trait::AutoTraitFinder;
+use crate::clean::blanket_impl::BlanketImplFinder;
+use crate::clean::render_macro_matchers::render_macro_matcher;
+use crate::clean::{
+ clean_middle_const, clean_middle_region, clean_middle_ty, inline, Clean, Crate, ExternalCrate,
+ Generic, GenericArg, GenericArgs, ImportSource, Item, ItemKind, Lifetime, Path, PathSegment,
+ Primitive, PrimitiveType, Type, TypeBinding, Visibility,
+};
+use crate::core::DocContext;
+use crate::formats::item_type::ItemType;
+use crate::visit_lib::LibEmbargoVisitor;
+
+use rustc_ast as ast;
+use rustc_ast::tokenstream::TokenTree;
+use rustc_data_structures::thin_vec::ThinVec;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE};
+use rustc_middle::mir;
+use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
+use rustc_middle::ty::{self, DefIdTree, TyCtxt};
+use rustc_span::symbol::{kw, sym, Symbol};
+use std::fmt::Write as _;
+use std::mem;
+
+#[cfg(test)]
+mod tests;
+
+pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
+ let module = crate::visit_ast::RustdocVisitor::new(cx).visit();
+
+ for &cnum in cx.tcx.crates(()) {
+ // Analyze doc-reachability for extern items
+ LibEmbargoVisitor::new(cx).visit_lib(cnum);
+ }
+
+ // Clean the crate, translating the entire librustc_ast AST to one that is
+ // understood by rustdoc.
+ let mut module = module.clean(cx);
+
+ match *module.kind {
+ ItemKind::ModuleItem(ref module) => {
+ for it in &module.items {
+ // `compiler_builtins` should be masked too, but we can't apply
+ // `#[doc(masked)]` to the injected `extern crate` because it's unstable.
+ if it.is_extern_crate()
+ && (it.attrs.has_doc_flag(sym::masked)
+ || cx.tcx.is_compiler_builtins(it.item_id.krate()))
+ {
+ cx.cache.masked_crates.insert(it.item_id.krate());
+ }
+ }
+ }
+ _ => unreachable!(),
+ }
+
+ let local_crate = ExternalCrate { crate_num: LOCAL_CRATE };
+ let primitives = local_crate.primitives(cx.tcx);
+ let keywords = local_crate.keywords(cx.tcx);
+ {
+ let ItemKind::ModuleItem(ref mut m) = *module.kind
+ else { unreachable!() };
+ m.items.extend(primitives.iter().map(|&(def_id, prim)| {
+ Item::from_def_id_and_parts(
+ def_id,
+ Some(prim.as_sym()),
+ ItemKind::PrimitiveItem(prim),
+ cx,
+ )
+ }));
+ m.items.extend(keywords.into_iter().map(|(def_id, kw)| {
+ Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::KeywordItem, cx)
+ }));
+ }
+
+ Crate { module, primitives, external_traits: cx.external_traits.clone() }
+}
+
+pub(crate) fn substs_to_args<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ substs: &[ty::subst::GenericArg<'tcx>],
+ mut skip_first: bool,
+) -> Vec<GenericArg> {
+ let mut ret_val =
+ Vec::with_capacity(substs.len().saturating_sub(if skip_first { 1 } else { 0 }));
+ ret_val.extend(substs.iter().filter_map(|kind| match kind.unpack() {
+ GenericArgKind::Lifetime(lt) => {
+ Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
+ }
+ GenericArgKind::Type(_) if skip_first => {
+ skip_first = false;
+ None
+ }
+ GenericArgKind::Type(ty) => Some(GenericArg::Type(clean_middle_ty(ty, cx, None))),
+ GenericArgKind::Const(ct) => Some(GenericArg::Const(Box::new(clean_middle_const(ct, cx)))),
+ }));
+ ret_val
+}
+
+fn external_generic_args<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ did: DefId,
+ has_self: bool,
+ bindings: Vec<TypeBinding>,
+ substs: SubstsRef<'tcx>,
+) -> GenericArgs {
+ let args = substs_to_args(cx, substs, has_self);
+
+ if cx.tcx.fn_trait_kind_from_lang_item(did).is_some() {
+ let inputs =
+ // The trait's first substitution is the one after self, if there is one.
+ match substs.iter().nth(if has_self { 1 } else { 0 }).unwrap().expect_ty().kind() {
+ ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(t, cx, None)).collect::<Vec<_>>().into(),
+ _ => return GenericArgs::AngleBracketed { args: args.into(), bindings: bindings.into() },
+ };
+ let output = None;
+ // FIXME(#20299) return type comes from a projection now
+ // match types[1].kind {
+ // ty::Tuple(ref v) if v.is_empty() => None, // -> ()
+ // _ => Some(types[1].clean(cx))
+ // };
+ GenericArgs::Parenthesized { inputs, output }
+ } else {
+ GenericArgs::AngleBracketed { args: args.into(), bindings: bindings.into() }
+ }
+}
+
+pub(super) fn external_path<'tcx>(
+ cx: &mut DocContext<'tcx>,
+ did: DefId,
+ has_self: bool,
+ bindings: Vec<TypeBinding>,
+ substs: SubstsRef<'tcx>,
+) -> Path {
+ let def_kind = cx.tcx.def_kind(did);
+ let name = cx.tcx.item_name(did);
+ Path {
+ res: Res::Def(def_kind, did),
+ segments: vec![PathSegment {
+ name,
+ args: external_generic_args(cx, did, has_self, bindings, substs),
+ }],
+ }
+}
+
+/// Remove the generic arguments from a path.
+pub(crate) fn strip_path_generics(mut path: Path) -> Path {
+ for ps in path.segments.iter_mut() {
+ ps.args = GenericArgs::AngleBracketed { args: Default::default(), bindings: ThinVec::new() }
+ }
+
+ path
+}
+
+pub(crate) fn qpath_to_string(p: &hir::QPath<'_>) -> String {
+ let segments = match *p {
+ hir::QPath::Resolved(_, path) => &path.segments,
+ hir::QPath::TypeRelative(_, segment) => return segment.ident.to_string(),
+ hir::QPath::LangItem(lang_item, ..) => return lang_item.name().to_string(),
+ };
+
+ let mut s = String::new();
+ for (i, seg) in segments.iter().enumerate() {
+ if i > 0 {
+ s.push_str("::");
+ }
+ if seg.ident.name != kw::PathRoot {
+ s.push_str(seg.ident.as_str());
+ }
+ }
+ s
+}
+
+pub(crate) fn build_deref_target_impls(
+ cx: &mut DocContext<'_>,
+ items: &[Item],
+ ret: &mut Vec<Item>,
+) {
+ let tcx = cx.tcx;
+
+ for item in items {
+ let target = match *item.kind {
+ ItemKind::AssocTypeItem(ref t, _) => &t.type_,
+ _ => continue,
+ };
+
+ if let Some(prim) = target.primitive_type() {
+ let _prof_timer = cx.tcx.sess.prof.generic_activity("build_primitive_inherent_impls");
+ for did in prim.impls(tcx).filter(|did| !did.is_local()) {
+ inline::build_impl(cx, None, did, None, ret);
+ }
+ } else if let Type::Path { path } = target {
+ let did = path.def_id();
+ if !did.is_local() {
+ inline::build_impls(cx, None, did, None, ret);
+ }
+ }
+ }
+}
+
+pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol {
+ use rustc_hir::*;
+ debug!("trying to get a name from pattern: {:?}", p);
+
+ Symbol::intern(&match p.kind {
+ PatKind::Wild | PatKind::Struct(..) => return kw::Underscore,
+ PatKind::Binding(_, _, ident, _) => return ident.name,
+ PatKind::TupleStruct(ref p, ..) | PatKind::Path(ref p) => qpath_to_string(p),
+ PatKind::Or(pats) => {
+ pats.iter().map(|p| name_from_pat(p).to_string()).collect::<Vec<String>>().join(" | ")
+ }
+ PatKind::Tuple(elts, _) => format!(
+ "({})",
+ elts.iter().map(|p| name_from_pat(p).to_string()).collect::<Vec<String>>().join(", ")
+ ),
+ PatKind::Box(p) => return name_from_pat(&*p),
+ PatKind::Ref(p, _) => return name_from_pat(&*p),
+ PatKind::Lit(..) => {
+ warn!(
+ "tried to get argument name from PatKind::Lit, which is silly in function arguments"
+ );
+ return Symbol::intern("()");
+ }
+ PatKind::Range(..) => return kw::Underscore,
+ PatKind::Slice(begin, ref mid, end) => {
+ let begin = begin.iter().map(|p| name_from_pat(p).to_string());
+ let mid = mid.as_ref().map(|p| format!("..{}", name_from_pat(&**p))).into_iter();
+ let end = end.iter().map(|p| name_from_pat(p).to_string());
+ format!("[{}]", begin.chain(mid).chain(end).collect::<Vec<_>>().join(", "))
+ }
+ })
+}
+
+pub(crate) fn print_const(cx: &DocContext<'_>, n: ty::Const<'_>) -> String {
+ match n.kind() {
+ ty::ConstKind::Unevaluated(ty::Unevaluated { def, substs: _, promoted }) => {
+ let mut s = if let Some(def) = def.as_local() {
+ print_const_expr(cx.tcx, cx.tcx.hir().body_owned_by(def.did))
+ } else {
+ inline::print_inlined_const(cx.tcx, def.did)
+ };
+ if let Some(promoted) = promoted {
+ s.push_str(&format!("::{:?}", promoted))
+ }
+ s
+ }
+ _ => {
+ let mut s = n.to_string();
+ // array lengths are obviously usize
+ if s.ends_with("_usize") {
+ let n = s.len() - "_usize".len();
+ s.truncate(n);
+ if s.ends_with(": ") {
+ let n = s.len() - ": ".len();
+ s.truncate(n);
+ }
+ }
+ s
+ }
+ }
+}
+
+pub(crate) fn print_evaluated_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option<String> {
+ tcx.const_eval_poly(def_id).ok().and_then(|val| {
+ let ty = tcx.type_of(def_id);
+ match (val, ty.kind()) {
+ (_, &ty::Ref(..)) => None,
+ (ConstValue::Scalar(_), &ty::Adt(_, _)) => None,
+ (ConstValue::Scalar(_), _) => {
+ let const_ = mir::ConstantKind::from_value(val, ty);
+ Some(print_const_with_custom_print_scalar(tcx, const_))
+ }
+ _ => None,
+ }
+ })
+}
+
+fn format_integer_with_underscore_sep(num: &str) -> String {
+ let num_chars: Vec<_> = num.chars().collect();
+ let mut num_start_index = if num_chars.get(0) == Some(&'-') { 1 } else { 0 };
+ let chunk_size = match num[num_start_index..].as_bytes() {
+ [b'0', b'b' | b'x', ..] => {
+ num_start_index += 2;
+ 4
+ }
+ [b'0', b'o', ..] => {
+ num_start_index += 2;
+ let remaining_chars = num_chars.len() - num_start_index;
+ if remaining_chars <= 6 {
+ // don't add underscores to Unix permissions like 0755 or 100755
+ return num.to_string();
+ }
+ 3
+ }
+ _ => 3,
+ };
+
+ num_chars[..num_start_index]
+ .iter()
+ .chain(num_chars[num_start_index..].rchunks(chunk_size).rev().intersperse(&['_']).flatten())
+ .collect()
+}
+
+fn print_const_with_custom_print_scalar(tcx: TyCtxt<'_>, ct: mir::ConstantKind<'_>) -> String {
+ // Use a slightly different format for integer types which always shows the actual value.
+ // For all other types, fallback to the original `pretty_print_const`.
+ match (ct, ct.ty().kind()) {
+ (mir::ConstantKind::Val(ConstValue::Scalar(int), _), ty::Uint(ui)) => {
+ format!("{}{}", format_integer_with_underscore_sep(&int.to_string()), ui.name_str())
+ }
+ (mir::ConstantKind::Val(ConstValue::Scalar(int), _), ty::Int(i)) => {
+ let ty = tcx.lift(ct.ty()).unwrap();
+ let size = tcx.layout_of(ty::ParamEnv::empty().and(ty)).unwrap().size;
+ let data = int.assert_bits(size);
+ let sign_extended_data = size.sign_extend(data) as i128;
+ format!(
+ "{}{}",
+ format_integer_with_underscore_sep(&sign_extended_data.to_string()),
+ i.name_str()
+ )
+ }
+ _ => ct.to_string(),
+ }
+}
+
+pub(crate) fn is_literal_expr(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
+ if let hir::Node::Expr(expr) = tcx.hir().get(hir_id) {
+ if let hir::ExprKind::Lit(_) = &expr.kind {
+ return true;
+ }
+
+ if let hir::ExprKind::Unary(hir::UnOp::Neg, expr) = &expr.kind {
+ if let hir::ExprKind::Lit(_) = &expr.kind {
+ return true;
+ }
+ }
+ }
+
+ false
+}
+
+/// Build a textual representation of an unevaluated constant expression.
+///
+/// If the const expression is too complex, an underscore `_` is returned.
+/// For const arguments, it's `{ _ }` to be precise.
+/// This means that the output is not necessarily valid Rust code.
+///
+/// Currently, only
+///
+/// * literals (optionally with a leading `-`)
+/// * unit `()`
+/// * blocks (`{ … }`) around simple expressions and
+/// * paths without arguments
+///
+/// are considered simple enough. Simple blocks are included since they are
+/// necessary to disambiguate unit from the unit type.
+/// This list might get extended in the future.
+///
+/// Without this censoring, in a lot of cases the output would get too large
+/// and verbose. Consider `match` expressions, blocks and deeply nested ADTs.
+/// Further, private and `doc(hidden)` fields of structs would get leaked
+/// since HIR datatypes like the `body` parameter do not contain enough
+/// semantic information for this function to be able to hide them –
+/// at least not without significant performance overhead.
+///
+/// Whenever possible, prefer to evaluate the constant first and try to
+/// use a different method for pretty-printing. Ideally this function
+/// should only ever be used as a fallback.
+pub(crate) fn print_const_expr(tcx: TyCtxt<'_>, body: hir::BodyId) -> String {
+ let hir = tcx.hir();
+ let value = &hir.body(body).value;
+
+ #[derive(PartialEq, Eq)]
+ enum Classification {
+ Literal,
+ Simple,
+ Complex,
+ }
+
+ use Classification::*;
+
+ fn classify(expr: &hir::Expr<'_>) -> Classification {
+ match &expr.kind {
+ hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
+ if matches!(expr.kind, hir::ExprKind::Lit(_)) { Literal } else { Complex }
+ }
+ hir::ExprKind::Lit(_) => Literal,
+ hir::ExprKind::Tup([]) => Simple,
+ hir::ExprKind::Block(hir::Block { stmts: [], expr: Some(expr), .. }, _) => {
+ if classify(expr) == Complex { Complex } else { Simple }
+ }
+ // Paths with a self-type or arguments are too “complex” following our measure since
+ // they may leak private fields of structs (with feature `adt_const_params`).
+ // Consider: `<Self as Trait<{ Struct { private: () } }>>::CONSTANT`.
+ // Paths without arguments are definitely harmless though.
+ hir::ExprKind::Path(hir::QPath::Resolved(_, hir::Path { segments, .. })) => {
+ if segments.iter().all(|segment| segment.args.is_none()) { Simple } else { Complex }
+ }
+ // FIXME: Claiming that those kinds of QPaths are simple is probably not true if the Ty
+ // contains const arguments. Is there a *concise* way to check for this?
+ hir::ExprKind::Path(hir::QPath::TypeRelative(..)) => Simple,
+ // FIXME: Can they contain const arguments and thus leak private struct fields?
+ hir::ExprKind::Path(hir::QPath::LangItem(..)) => Simple,
+ _ => Complex,
+ }
+ }
+
+ let classification = classify(value);
+
+ if classification == Literal
+ && !value.span.from_expansion()
+ && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(value.span) {
+ // For literals, we avoid invoking the pretty-printer and use the source snippet instead to
+ // preserve certain stylistic choices the user likely made for the sake legibility like
+ //
+ // * hexadecimal notation
+ // * underscores
+ // * character escapes
+ //
+ // FIXME: This passes through `-/*spacer*/0` verbatim.
+ snippet
+ } else if classification == Simple {
+ // Otherwise we prefer pretty-printing to get rid of extraneous whitespace, comments and
+ // other formatting artifacts.
+ rustc_hir_pretty::id_to_string(&hir, body.hir_id)
+ } else if tcx.def_kind(hir.body_owner_def_id(body).to_def_id()) == DefKind::AnonConst {
+ // FIXME: Omit the curly braces if the enclosing expression is an array literal
+ // with a repeated element (an `ExprKind::Repeat`) as in such case it
+ // would not actually need any disambiguation.
+ "{ _ }".to_owned()
+ } else {
+ "_".to_owned()
+ }
+}
+
+/// Given a type Path, resolve it to a Type using the TyCtxt
+pub(crate) fn resolve_type(cx: &mut DocContext<'_>, path: Path) -> Type {
+ debug!("resolve_type({:?})", path);
+
+ match path.res {
+ Res::PrimTy(p) => Primitive(PrimitiveType::from(p)),
+ Res::SelfTy { .. } if path.segments.len() == 1 => Generic(kw::SelfUpper),
+ Res::Def(DefKind::TyParam, _) if path.segments.len() == 1 => Generic(path.segments[0].name),
+ _ => {
+ let _ = register_res(cx, path.res);
+ Type::Path { path }
+ }
+ }
+}
+
+pub(crate) fn get_auto_trait_and_blanket_impls(
+ cx: &mut DocContext<'_>,
+ item_def_id: DefId,
+) -> impl Iterator<Item = Item> {
+ let auto_impls = cx
+ .sess()
+ .prof
+ .generic_activity("get_auto_trait_impls")
+ .run(|| AutoTraitFinder::new(cx).get_auto_trait_impls(item_def_id));
+ let blanket_impls = cx
+ .sess()
+ .prof
+ .generic_activity("get_blanket_impls")
+ .run(|| BlanketImplFinder { cx }.get_blanket_impls(item_def_id));
+ auto_impls.into_iter().chain(blanket_impls)
+}
+
+/// If `res` has a documentation page associated, store it in the cache.
+///
+/// This is later used by [`href()`] to determine the HTML link for the item.
+///
+/// [`href()`]: crate::html::format::href
+pub(crate) fn register_res(cx: &mut DocContext<'_>, res: Res) -> DefId {
+ use DefKind::*;
+ debug!("register_res({:?})", res);
+
+ let (did, kind) = match res {
+ // These should be added to the cache using `record_extern_fqn`.
+ Res::Def(
+ kind @ (AssocTy | AssocFn | AssocConst | Variant | Fn | TyAlias | Enum | Trait | Struct
+ | Union | Mod | ForeignTy | Const | Static(_) | Macro(..) | TraitAlias),
+ i,
+ ) => (i, kind.into()),
+ // This is part of a trait definition or trait impl; document the trait.
+ Res::SelfTy { trait_: Some(trait_def_id), alias_to: _ } => (trait_def_id, ItemType::Trait),
+ // This is an inherent impl or a type definition; it doesn't have its own page.
+ Res::SelfTy { trait_: None, alias_to: Some((item_def_id, _)) } => return item_def_id,
+ Res::SelfTy { trait_: None, alias_to: None }
+ | Res::PrimTy(_)
+ | Res::ToolMod
+ | Res::SelfCtor(_)
+ | Res::Local(_)
+ | Res::NonMacroAttr(_)
+ | Res::Err => return res.def_id(),
+ Res::Def(
+ TyParam | ConstParam | Ctor(..) | ExternCrate | Use | ForeignMod | AnonConst
+ | InlineConst | OpaqueTy | Field | LifetimeParam | GlobalAsm | Impl | Closure
+ | Generator,
+ id,
+ ) => return id,
+ };
+ if did.is_local() {
+ return did;
+ }
+ inline::record_extern_fqn(cx, did, kind);
+ if let ItemType::Trait = kind {
+ inline::record_extern_trait(cx, did);
+ }
+ did
+}
+
+pub(crate) fn resolve_use_source(cx: &mut DocContext<'_>, path: Path) -> ImportSource {
+ ImportSource {
+ did: if path.res.opt_def_id().is_none() { None } else { Some(register_res(cx, path.res)) },
+ path,
+ }
+}
+
+pub(crate) fn enter_impl_trait<'tcx, F, R>(cx: &mut DocContext<'tcx>, f: F) -> R
+where
+ F: FnOnce(&mut DocContext<'tcx>) -> R,
+{
+ let old_bounds = mem::take(&mut cx.impl_trait_bounds);
+ let r = f(cx);
+ assert!(cx.impl_trait_bounds.is_empty());
+ cx.impl_trait_bounds = old_bounds;
+ r
+}
+
+/// Find the nearest parent module of a [`DefId`].
+pub(crate) fn find_nearest_parent_module(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
+ if def_id.is_top_level_module() {
+ // The crate root has no parent. Use it as the root instead.
+ Some(def_id)
+ } else {
+ let mut current = def_id;
+ // The immediate parent might not always be a module.
+ // Find the first parent which is.
+ while let Some(parent) = tcx.opt_parent(current) {
+ if tcx.def_kind(parent) == DefKind::Mod {
+ return Some(parent);
+ }
+ current = parent;
+ }
+ None
+ }
+}
+
+/// Checks for the existence of `hidden` in the attribute below if `flag` is `sym::hidden`:
+///
+/// ```
+/// #[doc(hidden)]
+/// pub fn foo() {}
+/// ```
+///
+/// This function exists because it runs on `hir::Attributes` whereas the other is a
+/// `clean::Attributes` method.
+pub(crate) fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool {
+ tcx.get_attrs(did, sym::doc).any(|attr| {
+ attr.meta_item_list().map_or(false, |l| rustc_attr::list_contains_name(&l, flag))
+ })
+}
+
+/// A link to `doc.rust-lang.org` that includes the channel name. Use this instead of manual links
+/// so that the channel is consistent.
+///
+/// Set by `bootstrap::Builder::doc_rust_lang_org_channel` in order to keep tests passing on beta/stable.
+pub(crate) const DOC_RUST_LANG_ORG_CHANNEL: &str = env!("DOC_RUST_LANG_ORG_CHANNEL");
+
+/// Render a sequence of macro arms in a format suitable for displaying to the user
+/// as part of an item declaration.
+pub(super) fn render_macro_arms<'a>(
+ tcx: TyCtxt<'_>,
+ matchers: impl Iterator<Item = &'a TokenTree>,
+ arm_delim: &str,
+) -> String {
+ let mut out = String::new();
+ for matcher in matchers {
+ writeln!(out, " {} => {{ ... }}{}", render_macro_matcher(tcx, matcher), arm_delim)
+ .unwrap();
+ }
+ out
+}
+
+pub(super) fn display_macro_source(
+ cx: &mut DocContext<'_>,
+ name: Symbol,
+ def: &ast::MacroDef,
+ def_id: DefId,
+ vis: Visibility,
+) -> String {
+ let tts: Vec<_> = def.body.inner_tokens().into_trees().collect();
+ // Extract the spans of all matchers. They represent the "interface" of the macro.
+ let matchers = tts.chunks(4).map(|arm| &arm[0]);
+
+ if def.macro_rules {
+ format!("macro_rules! {} {{\n{}}}", name, render_macro_arms(cx.tcx, matchers, ";"))
+ } else {
+ if matchers.len() <= 1 {
+ format!(
+ "{}macro {}{} {{\n ...\n}}",
+ vis.to_src_with_space(cx.tcx, def_id),
+ name,
+ matchers.map(|matcher| render_macro_matcher(cx.tcx, matcher)).collect::<String>(),
+ )
+ } else {
+ format!(
+ "{}macro {} {{\n{}}}",
+ vis.to_src_with_space(cx.tcx, def_id),
+ name,
+ render_macro_arms(cx.tcx, matchers, ","),
+ )
+ }
+ }
+}
diff --git a/src/librustdoc/clean/utils/tests.rs b/src/librustdoc/clean/utils/tests.rs
new file mode 100644
index 000000000..ebf4b4954
--- /dev/null
+++ b/src/librustdoc/clean/utils/tests.rs
@@ -0,0 +1,41 @@
+use super::*;
+
+#[test]
+fn int_format_decimal() {
+ assert_eq!(format_integer_with_underscore_sep("12345678"), "12_345_678");
+ assert_eq!(format_integer_with_underscore_sep("123"), "123");
+ assert_eq!(format_integer_with_underscore_sep("123459"), "123_459");
+ assert_eq!(format_integer_with_underscore_sep("-12345678"), "-12_345_678");
+ assert_eq!(format_integer_with_underscore_sep("-123"), "-123");
+ assert_eq!(format_integer_with_underscore_sep("-123459"), "-123_459");
+}
+
+#[test]
+fn int_format_hex() {
+ assert_eq!(format_integer_with_underscore_sep("0xab3"), "0xab3");
+ assert_eq!(format_integer_with_underscore_sep("0xa2345b"), "0xa2_345b");
+ assert_eq!(format_integer_with_underscore_sep("0xa2e6345b"), "0xa2e6_345b");
+ assert_eq!(format_integer_with_underscore_sep("-0xab3"), "-0xab3");
+ assert_eq!(format_integer_with_underscore_sep("-0xa2345b"), "-0xa2_345b");
+ assert_eq!(format_integer_with_underscore_sep("-0xa2e6345b"), "-0xa2e6_345b");
+}
+
+#[test]
+fn int_format_binary() {
+ assert_eq!(format_integer_with_underscore_sep("0o12345671"), "0o12_345_671");
+ assert_eq!(format_integer_with_underscore_sep("0o123"), "0o123");
+ assert_eq!(format_integer_with_underscore_sep("0o123451"), "0o123451");
+ assert_eq!(format_integer_with_underscore_sep("-0o12345671"), "-0o12_345_671");
+ assert_eq!(format_integer_with_underscore_sep("-0o123"), "-0o123");
+ assert_eq!(format_integer_with_underscore_sep("-0o123451"), "-0o123451");
+}
+
+#[test]
+fn int_format_octal() {
+ assert_eq!(format_integer_with_underscore_sep("0b101"), "0b101");
+ assert_eq!(format_integer_with_underscore_sep("0b101101011"), "0b1_0110_1011");
+ assert_eq!(format_integer_with_underscore_sep("0b01101011"), "0b0110_1011");
+ assert_eq!(format_integer_with_underscore_sep("-0b101"), "-0b101");
+ assert_eq!(format_integer_with_underscore_sep("-0b101101011"), "-0b1_0110_1011");
+ assert_eq!(format_integer_with_underscore_sep("-0b01101011"), "-0b0110_1011");
+}