diff options
Diffstat (limited to 'src/librustdoc/clean')
-rw-r--r-- | src/librustdoc/clean/auto_trait.rs | 742 | ||||
-rw-r--r-- | src/librustdoc/clean/blanket_impl.rs | 136 | ||||
-rw-r--r-- | src/librustdoc/clean/cfg.rs | 588 | ||||
-rw-r--r-- | src/librustdoc/clean/cfg/tests.rs | 467 | ||||
-rw-r--r-- | src/librustdoc/clean/inline.rs | 727 | ||||
-rw-r--r-- | src/librustdoc/clean/mod.rs | 2242 | ||||
-rw-r--r-- | src/librustdoc/clean/render_macro_matchers.rs | 238 | ||||
-rw-r--r-- | src/librustdoc/clean/simplify.rs | 139 | ||||
-rw-r--r-- | src/librustdoc/clean/types.rs | 2508 | ||||
-rw-r--r-- | src/librustdoc/clean/types/tests.rs | 70 | ||||
-rw-r--r-- | src/librustdoc/clean/utils.rs | 616 | ||||
-rw-r--r-- | src/librustdoc/clean/utils/tests.rs | 41 |
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(®ion_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 ¶m.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(¶m_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 (¶m.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"); +} |