| //! This module defines the primary IR[^1] used in rustdoc together with the procedures that | |
| //! transform rustc data types into it. | |
| //! | |
| //! This IR — commonly referred to as the *cleaned AST* — is modeled after the [AST][rustc_ast]. | |
| //! | |
| //! There are two kinds of transformation — *cleaning* — procedures: | |
| //! | |
| //! 1. Cleans [HIR][hir] types. Used for user-written code and inlined local re-exports | |
| //! both found in the local crate. | |
| //! 2. Cleans [`rustc_middle::ty`] types. Used for inlined cross-crate re-exports and anything | |
| //! output by the trait solver (e.g., when synthesizing blanket and auto-trait impls). | |
| //! They usually have `ty` or `middle` in their name. | |
| //! | |
| //! Their name is prefixed by `clean_`. | |
| //! | |
| //! Both the HIR and the `rustc_middle::ty` IR are quite removed from the source code. | |
| //! The cleaned AST on the other hand is closer to it which simplifies the rendering process. | |
| //! Furthermore, operating on a single IR instead of two avoids duplicating efforts down the line. | |
| //! | |
| //! This IR is consumed by both the HTML and the JSON backend. | |
| //! | |
| //! [^1]: Intermediate representation. | |
| 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 std::borrow::Cow; | |
| use std::collections::BTreeMap; | |
| use std::mem; | |
| use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry}; | |
| use rustc_data_structures::thin_vec::ThinVec; | |
| use rustc_errors::codes::*; | |
| use rustc_errors::{FatalError, struct_span_code_err}; | |
| use rustc_hir as hir; | |
| use rustc_hir::attrs::{AttributeKind, DocAttribute, DocInline}; | |
| use rustc_hir::def::{CtorKind, DefKind, MacroKinds, Res}; | |
| use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE, LocalDefId}; | |
| use rustc_hir::{LangItem, PredicateOrigin, find_attr}; | |
| use rustc_hir_analysis::{lower_const_arg_for_rustdoc, lower_ty}; | |
| use rustc_middle::metadata::Reexport; | |
| use rustc_middle::middle::resolve_bound_vars as rbv; | |
| use rustc_middle::ty::{self, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, TypingMode}; | |
| use rustc_middle::{bug, span_bug}; | |
| use rustc_span::ExpnKind; | |
| use rustc_span::hygiene::{AstPass, MacroKind}; | |
| use rustc_span::symbol::{Ident, Symbol, kw}; | |
| use rustc_trait_selection::traits::wf::object_region_bounds; | |
| use tracing::{debug, instrument}; | |
| use utils::*; | |
| pub(crate) use self::cfg::{CfgInfo, extract_cfg_from_attrs}; | |
| pub(crate) use self::types::*; | |
| pub(crate) use self::utils::{krate, register_res, synthesize_auto_trait_and_blanket_impls}; | |
| use crate::core::DocContext; | |
| use crate::formats::item_type::ItemType; | |
| use crate::visit_ast::Module as DocModule; | |
| pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<'tcx>) -> Item { | |
| let mut items: Vec<Item> = vec![]; | |
| let mut inserted = FxHashSet::default(); | |
| items.extend(doc.foreigns.iter().map(|(item, renamed, import_id)| { | |
| let item = clean_maybe_renamed_foreign_item(cx, item, *renamed, *import_id); | |
| if let Some(name) = item.name | |
| && (cx.document_hidden() || !item.is_doc_hidden()) | |
| { | |
| inserted.insert((item.type_(), name)); | |
| } | |
| item | |
| })); | |
| items.extend(doc.mods.iter().filter_map(|x| { | |
| if !inserted.insert((ItemType::Module, x.name)) { | |
| return None; | |
| } | |
| let item = clean_doc_module(x, cx); | |
| if !cx.document_hidden() && item.is_doc_hidden() { | |
| // Hidden modules are stripped at a later stage. | |
| // If a hidden module has the same name as a visible one, we want | |
| // to keep both of them around. | |
| inserted.remove(&(ItemType::Module, x.name)); | |
| } | |
| Some(item) | |
| })); | |
| // Split up glob 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(doc.items.values().flat_map(|(item, renamed, import_ids)| { | |
| // First, lower everything other than glob imports. | |
| if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { | |
| return Vec::new(); | |
| } | |
| let v = clean_maybe_renamed_item(cx, item, *renamed, import_ids); | |
| for item in &v { | |
| if let Some(name) = item.name | |
| && (cx.document_hidden() || !item.is_doc_hidden()) | |
| { | |
| inserted.insert((item.type_(), name)); | |
| } | |
| } | |
| v | |
| })); | |
| items.extend(doc.inlined_foreigns.iter().flat_map(|((_, renamed), (res, local_import_id))| { | |
| let Some(def_id) = res.opt_def_id() else { return Vec::new() }; | |
| let name = renamed.unwrap_or_else(|| cx.tcx.item_name(def_id)); | |
| let import = cx.tcx.hir_expect_item(*local_import_id); | |
| match import.kind { | |
| hir::ItemKind::Use(path, kind) => { | |
| let hir::UsePath { segments, span, .. } = *path; | |
| let path = hir::Path { segments, res: *res, span }; | |
| clean_use_statement_inner( | |
| import, | |
| Some(name), | |
| &path, | |
| kind, | |
| cx, | |
| &mut Default::default(), | |
| ) | |
| } | |
| _ => unreachable!(), | |
| } | |
| })); | |
| items.extend(doc.items.values().flat_map(|(item, renamed, _)| { | |
| // Now we actually lower the imports, skipping everything else. | |
| if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind { | |
| clean_use_statement(item, *renamed, 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 = doc.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(doc.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) | |
| doc.where_inner | |
| } | |
| }); | |
| let kind = ModuleItem(Module { items, span }); | |
| generate_item_with_correct_attrs( | |
| cx, | |
| kind, | |
| doc.def_id.to_def_id(), | |
| doc.name, | |
| doc.import_id.as_slice(), | |
| doc.renamed, | |
| ) | |
| } | |
| fn is_glob_import(tcx: TyCtxt<'_>, import_id: LocalDefId) -> bool { | |
| if let hir::Node::Item(item) = tcx.hir_node_by_def_id(import_id) | |
| && let hir::ItemKind::Use(_, use_kind) = item.kind | |
| { | |
| use_kind == hir::UseKind::Glob | |
| } else { | |
| false | |
| } | |
| } | |
| fn generate_item_with_correct_attrs( | |
| cx: &mut DocContext<'_>, | |
| kind: ItemKind, | |
| def_id: DefId, | |
| name: Symbol, | |
| import_ids: &[LocalDefId], | |
| renamed: Option<Symbol>, | |
| ) -> Item { | |
| let target_attrs = inline::load_attrs(cx, def_id); | |
| let attrs = if !import_ids.is_empty() { | |
| let mut attrs = Vec::with_capacity(import_ids.len()); | |
| let mut is_inline = false; | |
| for import_id in import_ids.iter().copied() { | |
| // glob reexports are treated the same as `#[doc(inline)]` items. | |
| // | |
| // For glob re-exports the item may or may not exist to be re-exported (potentially the | |
| // cfgs on the path up until the glob can be removed, and only cfgs on the globbed item | |
| // itself matter), for non-inlined re-exports see #85043. | |
| let import_is_inline = find_attr!( | |
| inline::load_attrs(cx, import_id.to_def_id()), | |
| Doc(d) | |
| if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) | |
| ) || (is_glob_import(cx.tcx, import_id) | |
| && (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id))); | |
| attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); | |
| is_inline = is_inline || import_is_inline; | |
| } | |
| add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None); | |
| attrs | |
| } else { | |
| // We only keep the item's attributes. | |
| target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect() | |
| }; | |
| let attrs = Attributes::from_hir_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false); | |
| let name = renamed.or(Some(name)); | |
| let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, None); | |
| // FIXME (GuillaumeGomez): Should we also make `inline_stmt_id` a `Vec` instead of an `Option`? | |
| item.inner.inline_stmt_id = import_ids.first().copied(); | |
| item | |
| } | |
| fn clean_generic_bound<'tcx>( | |
| bound: &hir::GenericBound<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Option<GenericBound> { | |
| Some(match bound { | |
| hir::GenericBound::Outlives(lt) => GenericBound::Outlives(clean_lifetime(lt, cx)), | |
| hir::GenericBound::Trait(t) => { | |
| // `T: [const] Destruct` is hidden because `T: Destruct` is a no-op. | |
| if let hir::BoundConstness::Maybe(_) = t.modifiers.constness | |
| && cx.tcx.lang_items().destruct_trait() == Some(t.trait_ref.trait_def_id().unwrap()) | |
| { | |
| return None; | |
| } | |
| GenericBound::TraitBound(clean_poly_trait_ref(t, cx), t.modifiers) | |
| } | |
| hir::GenericBound::Use(args, ..) => { | |
| GenericBound::Use(args.iter().map(|arg| clean_precise_capturing_arg(arg, cx)).collect()) | |
| } | |
| }) | |
| } | |
| pub(crate) fn clean_trait_ref_with_constraints<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| trait_ref: ty::PolyTraitRef<'tcx>, | |
| constraints: ThinVec<AssocItemConstraint>, | |
| ) -> Path { | |
| let kind = ItemType::from_def_id(trait_ref.def_id(), cx.tcx); | |
| 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 = clean_middle_path( | |
| cx, | |
| trait_ref.def_id(), | |
| true, | |
| constraints, | |
| trait_ref.map_bound(|tr| tr.args), | |
| ); | |
| debug!(?trait_ref); | |
| path | |
| } | |
| fn clean_poly_trait_ref_with_constraints<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| poly_trait_ref: ty::PolyTraitRef<'tcx>, | |
| constraints: ThinVec<AssocItemConstraint>, | |
| ) -> GenericBound { | |
| GenericBound::TraitBound( | |
| PolyTrait { | |
| trait_: clean_trait_ref_with_constraints(cx, poly_trait_ref, constraints), | |
| generic_params: clean_bound_vars(poly_trait_ref.bound_vars(), cx), | |
| }, | |
| hir::TraitBoundModifiers::NONE, | |
| ) | |
| } | |
| fn clean_lifetime(lifetime: &hir::Lifetime, cx: &DocContext<'_>) -> Lifetime { | |
| if let Some( | |
| rbv::ResolvedArg::EarlyBound(did) | |
| | rbv::ResolvedArg::LateBound(_, _, did) | |
| | rbv::ResolvedArg::Free(_, did), | |
| ) = cx.tcx.named_bound_var(lifetime.hir_id) | |
| && let Some(lt) = cx.args.get(&did.to_def_id()).and_then(|arg| arg.as_lt()) | |
| { | |
| return *lt; | |
| } | |
| Lifetime(lifetime.ident.name) | |
| } | |
| pub(crate) fn clean_precise_capturing_arg( | |
| arg: &hir::PreciseCapturingArg<'_>, | |
| cx: &DocContext<'_>, | |
| ) -> PreciseCapturingArg { | |
| match arg { | |
| hir::PreciseCapturingArg::Lifetime(lt) => { | |
| PreciseCapturingArg::Lifetime(clean_lifetime(lt, cx)) | |
| } | |
| hir::PreciseCapturingArg::Param(param) => PreciseCapturingArg::Param(param.ident.name), | |
| } | |
| } | |
| pub(crate) fn clean_const_item_rhs<'tcx>( | |
| ct_rhs: hir::ConstItemRhs<'tcx>, | |
| parent: DefId, | |
| ) -> ConstantKind { | |
| match ct_rhs { | |
| hir::ConstItemRhs::Body(body) => ConstantKind::Local { def_id: parent, body }, | |
| hir::ConstItemRhs::TypeConst(ct) => clean_const(ct), | |
| } | |
| } | |
| pub(crate) fn clean_const<'tcx>(constant: &hir::ConstArg<'tcx>) -> ConstantKind { | |
| match &constant.kind { | |
| hir::ConstArgKind::Path(qpath) => { | |
| ConstantKind::Path { path: qpath_to_string(qpath).into() } | |
| } | |
| hir::ConstArgKind::Struct(..) => { | |
| // FIXME(mgca): proper printing :3 | |
| ConstantKind::Path { path: "/* STRUCT EXPR */".to_string().into() } | |
| } | |
| hir::ConstArgKind::TupleCall(..) => { | |
| ConstantKind::Path { path: "/* TUPLE CALL */".to_string().into() } | |
| } | |
| hir::ConstArgKind::Tup(..) => { | |
| // FIXME(mgca): proper printing :3 | |
| ConstantKind::Path { path: "/* TUPLE EXPR */".to_string().into() } | |
| } | |
| hir::ConstArgKind::Array(..) => { | |
| ConstantKind::Path { path: "/* ARRAY EXPR */".to_string().into() } | |
| } | |
| hir::ConstArgKind::Anon(anon) => ConstantKind::Anonymous { body: anon.body }, | |
| hir::ConstArgKind::Infer(..) | hir::ConstArgKind::Error(..) => ConstantKind::Infer, | |
| hir::ConstArgKind::Literal { .. } => { | |
| ConstantKind::Path { path: "/* LITERAL */".to_string().into() } | |
| } | |
| } | |
| } | |
| pub(crate) fn clean_middle_const<'tcx>( | |
| constant: ty::Binder<'tcx, ty::Const<'tcx>>, | |
| _cx: &mut DocContext<'tcx>, | |
| ) -> ConstantKind { | |
| // FIXME: instead of storing the stringified expression, store `self` directly instead. | |
| ConstantKind::TyConst { expr: constant.skip_binder().to_string().into() } | |
| } | |
| pub(crate) fn clean_middle_region<'tcx>( | |
| region: ty::Region<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Option<Lifetime> { | |
| region.get_name(cx.tcx).map(Lifetime) | |
| } | |
| fn clean_where_predicate<'tcx>( | |
| predicate: &hir::WherePredicate<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Option<WherePredicate> { | |
| if !predicate.kind.in_where_clause() { | |
| return None; | |
| } | |
| Some(match predicate.kind { | |
| hir::WherePredicateKind::BoundPredicate(wbp) => { | |
| let bound_params = wbp | |
| .bound_generic_params | |
| .iter() | |
| .map(|param| clean_generic_param(cx, None, param)) | |
| .collect(); | |
| WherePredicate::BoundPredicate { | |
| ty: clean_ty(wbp.bounded_ty, cx), | |
| bounds: wbp.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(), | |
| bound_params, | |
| } | |
| } | |
| hir::WherePredicateKind::RegionPredicate(wrp) => WherePredicate::RegionPredicate { | |
| lifetime: clean_lifetime(wrp.lifetime, cx), | |
| bounds: wrp.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(), | |
| }, | |
| // We should never actually reach this case because these predicates should've already been | |
| // rejected in an earlier compiler pass. This feature isn't fully implemented (#20041). | |
| hir::WherePredicateKind::EqPredicate(_) => bug!("EqPredicate"), | |
| }) | |
| } | |
| pub(crate) fn clean_predicate<'tcx>( | |
| predicate: ty::Clause<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Option<WherePredicate> { | |
| let bound_predicate = predicate.kind(); | |
| match bound_predicate.skip_binder() { | |
| ty::ClauseKind::Trait(pred) => clean_poly_trait_predicate(bound_predicate.rebind(pred), cx), | |
| ty::ClauseKind::RegionOutlives(pred) => Some(clean_region_outlives_predicate(pred, cx)), | |
| ty::ClauseKind::TypeOutlives(pred) => { | |
| Some(clean_type_outlives_predicate(bound_predicate.rebind(pred), cx)) | |
| } | |
| ty::ClauseKind::Projection(pred) => { | |
| Some(clean_projection_predicate(bound_predicate.rebind(pred), cx)) | |
| } | |
| // FIXME(generic_const_exprs): should this do something? | |
| ty::ClauseKind::ConstEvaluatable(..) | |
| | ty::ClauseKind::WellFormed(..) | |
| | ty::ClauseKind::ConstArgHasType(..) | |
| | ty::ClauseKind::UnstableFeature(..) | |
| // FIXME(const_trait_impl): We can probably use this `HostEffect` pred to render `~const`. | |
| | ty::ClauseKind::HostEffect(_) => None, | |
| } | |
| } | |
| 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. | |
| // FIXME(const_trait_impl) check constness | |
| if 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.self_ty(), cx, None, None), | |
| bounds: vec![clean_poly_trait_ref_with_constraints(cx, poly_trait_ref, ThinVec::new())], | |
| bound_params: Vec::new(), | |
| }) | |
| } | |
| fn clean_region_outlives_predicate<'tcx>( | |
| pred: ty::RegionOutlivesPredicate<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> WherePredicate { | |
| let ty::OutlivesPredicate(a, b) = pred; | |
| WherePredicate::RegionPredicate { | |
| lifetime: clean_middle_region(a, cx).expect("failed to clean lifetime"), | |
| bounds: vec![GenericBound::Outlives( | |
| clean_middle_region(b, cx).expect("failed to clean bounds"), | |
| )], | |
| } | |
| } | |
| fn clean_type_outlives_predicate<'tcx>( | |
| pred: ty::Binder<'tcx, ty::TypeOutlivesPredicate<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> WherePredicate { | |
| let ty::OutlivesPredicate(ty, lt) = pred.skip_binder(); | |
| WherePredicate::BoundPredicate { | |
| ty: clean_middle_ty(pred.rebind(ty), cx, None, None), | |
| bounds: vec![GenericBound::Outlives( | |
| clean_middle_region(lt, cx).expect("failed to clean lifetimes"), | |
| )], | |
| bound_params: Vec::new(), | |
| } | |
| } | |
| fn clean_middle_term<'tcx>( | |
| term: ty::Binder<'tcx, ty::Term<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Term { | |
| match term.skip_binder().kind() { | |
| ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None, None)), | |
| ty::TermKind::Const(c) => Term::Constant(clean_middle_const(term.rebind(c), cx)), | |
| } | |
| } | |
| fn clean_hir_term<'tcx>( | |
| assoc_item: Option<DefId>, | |
| 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) => { | |
| // FIXME(generic_const_items): this should instantiate with the alias item's args | |
| let ty = cx.tcx.type_of(assoc_item.unwrap()).instantiate_identity(); | |
| let ct = lower_const_arg_for_rustdoc(cx.tcx, c, ty); | |
| Term::Constant(clean_middle_const(ty::Binder::dummy(ct), cx)) | |
| } | |
| } | |
| } | |
| fn clean_projection_predicate<'tcx>( | |
| pred: ty::Binder<'tcx, ty::ProjectionPredicate<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> WherePredicate { | |
| WherePredicate::EqPredicate { | |
| lhs: clean_projection(pred.map_bound(|p| p.projection_term), cx, None), | |
| rhs: clean_middle_term(pred.map_bound(|p| p.term), cx), | |
| } | |
| } | |
| fn clean_projection<'tcx>( | |
| proj: ty::Binder<'tcx, ty::AliasTerm<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| parent_def_id: Option<DefId>, | |
| ) -> QPathData { | |
| let trait_ = clean_trait_ref_with_constraints( | |
| cx, | |
| proj.map_bound(|proj| proj.trait_ref(cx.tcx)), | |
| ThinVec::new(), | |
| ); | |
| let self_type = clean_middle_ty(proj.map_bound(|proj| proj.self_ty()), cx, None, None); | |
| let self_def_id = match parent_def_id { | |
| Some(parent_def_id) => cx.tcx.opt_parent(parent_def_id).or(Some(parent_def_id)), | |
| None => self_type.def_id(&cx.cache), | |
| }; | |
| let should_fully_qualify = should_fully_qualify_path(self_def_id, &trait_, &self_type); | |
| QPathData { | |
| assoc: projection_to_path_segment(proj, cx), | |
| self_type, | |
| should_fully_qualify, | |
| trait_: Some(trait_), | |
| } | |
| } | |
| fn should_fully_qualify_path(self_def_id: Option<DefId>, trait_: &Path, self_type: &Type) -> bool { | |
| !trait_.segments.is_empty() | |
| && self_def_id | |
| .zip(Some(trait_.def_id())) | |
| .map_or(!self_type.is_self_type(), |(id, trait_)| id != trait_) | |
| } | |
| fn projection_to_path_segment<'tcx>( | |
| proj: ty::Binder<'tcx, ty::AliasTerm<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> PathSegment { | |
| let def_id = proj.skip_binder().def_id; | |
| let generics = cx.tcx.generics_of(def_id); | |
| PathSegment { | |
| name: cx.tcx.item_name(def_id), | |
| args: GenericArgs::AngleBracketed { | |
| args: clean_middle_generic_args( | |
| cx, | |
| proj.map_bound(|ty| &ty.args[generics.parent_count..]), | |
| false, | |
| def_id, | |
| ), | |
| constraints: Default::default(), | |
| }, | |
| } | |
| } | |
| fn clean_generic_param_def( | |
| def: &ty::GenericParamDef, | |
| defaults: ParamDefaults, | |
| cx: &mut DocContext<'_>, | |
| ) -> GenericParamDef { | |
| let (name, kind) = match def.kind { | |
| ty::GenericParamDefKind::Lifetime => { | |
| (def.name, GenericParamDefKind::Lifetime { outlives: ThinVec::new() }) | |
| } | |
| ty::GenericParamDefKind::Type { has_default, synthetic, .. } => { | |
| let default = if let ParamDefaults::Yes = defaults | |
| && has_default | |
| { | |
| Some(clean_middle_ty( | |
| ty::Binder::dummy(cx.tcx.type_of(def.def_id).instantiate_identity()), | |
| cx, | |
| Some(def.def_id), | |
| None, | |
| )) | |
| } else { | |
| None | |
| }; | |
| ( | |
| def.name, | |
| GenericParamDefKind::Type { | |
| bounds: ThinVec::new(), // These are filled in from the where-clauses. | |
| default: default.map(Box::new), | |
| synthetic, | |
| }, | |
| ) | |
| } | |
| ty::GenericParamDefKind::Const { has_default } => ( | |
| def.name, | |
| GenericParamDefKind::Const { | |
| ty: Box::new(clean_middle_ty( | |
| ty::Binder::dummy(cx.tcx.type_of(def.def_id).instantiate_identity()), | |
| cx, | |
| Some(def.def_id), | |
| None, | |
| )), | |
| default: if let ParamDefaults::Yes = defaults | |
| && has_default | |
| { | |
| Some(Box::new( | |
| cx.tcx.const_param_default(def.def_id).instantiate_identity().to_string(), | |
| )) | |
| } else { | |
| None | |
| }, | |
| }, | |
| ), | |
| }; | |
| GenericParamDef { name, def_id: def.def_id, kind } | |
| } | |
| /// Whether to clean generic parameter defaults or not. | |
| enum ParamDefaults { | |
| Yes, | |
| No, | |
| } | |
| fn clean_generic_param<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| generics: Option<&hir::Generics<'tcx>>, | |
| param: &hir::GenericParam<'tcx>, | |
| ) -> GenericParamDef { | |
| let (name, kind) = match param.kind { | |
| hir::GenericParamKind::Lifetime { .. } => { | |
| let outlives = if let Some(generics) = generics { | |
| generics | |
| .outlives_for_param(param.def_id) | |
| .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 { | |
| ThinVec::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(param.def_id) | |
| .filter(|bp| bp.origin != PredicateOrigin::WhereClause) | |
| .flat_map(|bp| bp.bounds) | |
| .filter_map(|x| clean_generic_bound(x, cx)) | |
| .collect() | |
| } else { | |
| ThinVec::new() | |
| }; | |
| ( | |
| param.name.ident().name, | |
| GenericParamDefKind::Type { | |
| bounds, | |
| default: default.map(|t| clean_ty(t, cx)).map(Box::new), | |
| synthetic, | |
| }, | |
| ) | |
| } | |
| hir::GenericParamKind::Const { ty, default } => ( | |
| param.name.ident().name, | |
| GenericParamDefKind::Const { | |
| ty: Box::new(clean_ty(ty, cx)), | |
| default: default.map(|ct| { | |
| Box::new( | |
| lower_const_arg_for_rustdoc(cx.tcx, ct, lower_ty(cx.tcx, ty)).to_string(), | |
| ) | |
| }), | |
| }, | |
| ), | |
| }; | |
| GenericParamDef { name, def_id: param.def_id.to_def_id(), 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(_) } | |
| ) | |
| } | |
| pub(crate) fn clean_generics<'tcx>( | |
| gens: &hir::Generics<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Generics { | |
| let impl_trait_params = gens | |
| .params | |
| .iter() | |
| .filter(|param| is_impl_trait(param)) | |
| .map(|param| { | |
| let param = clean_generic_param(cx, Some(gens), param); | |
| match param.kind { | |
| GenericParamDefKind::Lifetime { .. } => unreachable!(), | |
| GenericParamDefKind::Type { ref bounds, .. } => { | |
| cx.impl_trait_bounds.insert(param.def_id.into(), bounds.to_vec()); | |
| } | |
| GenericParamDefKind::Const { .. } => unreachable!(), | |
| } | |
| param | |
| }) | |
| .collect::<Vec<_>>(); | |
| let mut bound_predicates = FxIndexMap::default(); | |
| let mut region_predicates = FxIndexMap::default(); | |
| let mut eq_predicates = ThinVec::default(); | |
| for pred in gens.predicates.iter().filter_map(|x| clean_where_predicate(x, cx)) { | |
| match pred { | |
| WherePredicate::BoundPredicate { ty, bounds, bound_params } => { | |
| match bound_predicates.entry(ty) { | |
| IndexEntry::Vacant(v) => { | |
| v.insert((bounds, bound_params)); | |
| } | |
| IndexEntry::Occupied(mut o) => { | |
| // we merge both bounds. | |
| for bound in bounds { | |
| if !o.get().0.contains(&bound) { | |
| o.get_mut().0.push(bound); | |
| } | |
| } | |
| for bound_param in bound_params { | |
| if !o.get().1.contains(&bound_param) { | |
| o.get_mut().1.push(bound_param); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| WherePredicate::RegionPredicate { lifetime, bounds } => { | |
| match region_predicates.entry(lifetime) { | |
| IndexEntry::Vacant(v) => { | |
| v.insert(bounds); | |
| } | |
| IndexEntry::Occupied(mut o) => { | |
| // we merge both bounds. | |
| for bound in bounds { | |
| if !o.get().contains(&bound) { | |
| o.get_mut().push(bound); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| WherePredicate::EqPredicate { lhs, rhs } => { | |
| eq_predicates.push(WherePredicate::EqPredicate { lhs, rhs }); | |
| } | |
| } | |
| } | |
| let mut params = ThinVec::with_capacity(gens.params.len()); | |
| // In this loop, we gather the generic parameters (`<'a, B: 'a>`) and check if they have | |
| // bounds in the where predicates. If so, we move their bounds into the where predicates | |
| // while also preventing duplicates. | |
| for p in gens.params.iter().filter(|p| !is_impl_trait(p) && !is_elided_lifetime(p)) { | |
| let mut p = clean_generic_param(cx, Some(gens), p); | |
| match &mut p.kind { | |
| GenericParamDefKind::Lifetime { outlives } => { | |
| if let Some(region_pred) = region_predicates.get_mut(&Lifetime(p.name)) { | |
| // We merge bounds in the `where` clause. | |
| for outlive in outlives.drain(..) { | |
| let outlive = GenericBound::Outlives(outlive); | |
| if !region_pred.contains(&outlive) { | |
| region_pred.push(outlive); | |
| } | |
| } | |
| } | |
| } | |
| GenericParamDefKind::Type { bounds, synthetic: false, .. } => { | |
| if let Some(bound_pred) = bound_predicates.get_mut(&Type::Generic(p.name)) { | |
| // We merge bounds in the `where` clause. | |
| for bound in bounds.drain(..) { | |
| if !bound_pred.0.contains(&bound) { | |
| bound_pred.0.push(bound); | |
| } | |
| } | |
| } | |
| } | |
| GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { | |
| // nothing to do here. | |
| } | |
| } | |
| params.push(p); | |
| } | |
| params.extend(impl_trait_params); | |
| Generics { | |
| params, | |
| where_predicates: bound_predicates | |
| .into_iter() | |
| .map(|(ty, (bounds, bound_params))| WherePredicate::BoundPredicate { | |
| ty, | |
| bounds, | |
| bound_params, | |
| }) | |
| .chain( | |
| region_predicates | |
| .into_iter() | |
| .map(|(lifetime, bounds)| WherePredicate::RegionPredicate { lifetime, bounds }), | |
| ) | |
| .chain(eq_predicates) | |
| .collect(), | |
| } | |
| } | |
| fn clean_ty_generics<'tcx>(cx: &mut DocContext<'tcx>, def_id: DefId) -> Generics { | |
| clean_ty_generics_inner(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id)) | |
| } | |
| fn clean_ty_generics_inner<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| gens: &ty::Generics, | |
| preds: ty::GenericPredicates<'tcx>, | |
| ) -> Generics { | |
| // Don't populate `cx.impl_trait_bounds` before cleaning where clauses, | |
| // since `clean_predicate` would consume them. | |
| let mut impl_trait = BTreeMap::<u32, Vec<GenericBound>>::default(); | |
| let params: ThinVec<_> = gens | |
| .own_params | |
| .iter() | |
| .filter(|param| match param.kind { | |
| ty::GenericParamDefKind::Lifetime => !param.is_anonymous_lifetime(), | |
| ty::GenericParamDefKind::Type { synthetic, .. } => { | |
| if param.name == kw::SelfUpper { | |
| debug_assert_eq!(param.index, 0); | |
| return false; | |
| } | |
| if synthetic { | |
| impl_trait.insert(param.index, vec![]); | |
| return false; | |
| } | |
| true | |
| } | |
| ty::GenericParamDefKind::Const { .. } => true, | |
| }) | |
| .map(|param| clean_generic_param_def(param, ParamDefaults::Yes, cx)) | |
| .collect(); | |
| // param index -> [(trait DefId, associated type name & generics, term)] | |
| let mut impl_trait_proj = | |
| FxHashMap::<u32, Vec<(DefId, PathSegment, ty::Binder<'_, ty::Term<'_>>)>>::default(); | |
| let where_predicates = preds | |
| .predicates | |
| .iter() | |
| .flat_map(|(pred, _)| { | |
| let mut proj_pred = None; | |
| let param_idx = { | |
| let bound_p = pred.kind(); | |
| match bound_p.skip_binder() { | |
| ty::ClauseKind::Trait(pred) if let ty::Param(param) = pred.self_ty().kind() => { | |
| Some(param.index) | |
| } | |
| ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, _reg)) | |
| if let ty::Param(param) = ty.kind() => | |
| { | |
| Some(param.index) | |
| } | |
| ty::ClauseKind::Projection(p) | |
| if let ty::Param(param) = p.projection_term.self_ty().kind() => | |
| { | |
| proj_pred = Some(bound_p.rebind(p)); | |
| Some(param.index) | |
| } | |
| _ => None, | |
| } | |
| }; | |
| if let Some(param_idx) = param_idx | |
| && let Some(bounds) = impl_trait.get_mut(¶m_idx) | |
| { | |
| let pred = clean_predicate(*pred, cx)?; | |
| bounds.extend(pred.get_bounds().into_iter().flatten().cloned()); | |
| if let Some(pred) = proj_pred { | |
| let lhs = clean_projection(pred.map_bound(|p| p.projection_term), cx, None); | |
| impl_trait_proj.entry(param_idx).or_default().push(( | |
| lhs.trait_.unwrap().def_id(), | |
| lhs.assoc, | |
| pred.map_bound(|p| p.term), | |
| )); | |
| } | |
| return None; | |
| } | |
| Some(pred) | |
| }) | |
| .collect::<Vec<_>>(); | |
| for (idx, mut bounds) in impl_trait { | |
| let mut has_sized = false; | |
| bounds.retain(|b| { | |
| if b.is_sized_bound(cx) { | |
| has_sized = true; | |
| false | |
| } else if b.is_meta_sized_bound(cx) { | |
| // FIXME(sized-hierarchy): Always skip `MetaSized` bounds so that only `?Sized` | |
| // is shown and none of the new sizedness traits leak into documentation. | |
| false | |
| } else { | |
| true | |
| } | |
| }); | |
| if !has_sized { | |
| bounds.push(GenericBound::maybe_sized(cx)); | |
| } | |
| // Move trait bounds to the front. | |
| bounds.sort_by_key(|b| !b.is_trait_bound()); | |
| // Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`). | |
| // Since all potential trait bounds are at the front we can just check the first bound. | |
| if bounds.first().is_none_or(|b| !b.is_trait_bound()) { | |
| bounds.insert(0, GenericBound::sized(cx)); | |
| } | |
| if let Some(proj) = impl_trait_proj.remove(&idx) { | |
| for (trait_did, name, rhs) in proj { | |
| let rhs = clean_middle_term(rhs, cx); | |
| simplify::merge_bounds(cx, &mut bounds, trait_did, name, &rhs); | |
| } | |
| } | |
| cx.impl_trait_bounds.insert(idx.into(), bounds); | |
| } | |
| // Now that `cx.impl_trait_bounds` is populated, we can process | |
| // remaining predicates which could contain `impl Trait`. | |
| let where_predicates = | |
| where_predicates.into_iter().flat_map(|p| clean_predicate(*p, cx)).collect(); | |
| let mut generics = Generics { params, where_predicates }; | |
| simplify::sized_bounds(cx, &mut generics); | |
| generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates); | |
| generics | |
| } | |
| fn clean_ty_alias_inner_type<'tcx>( | |
| ty: Ty<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ret: &mut Vec<Item>, | |
| ) -> Option<TypeAliasInnerType> { | |
| let ty::Adt(adt_def, args) = ty.kind() else { | |
| return None; | |
| }; | |
| if !adt_def.did().is_local() { | |
| cx.with_param_env(adt_def.did(), |cx| { | |
| inline::build_impls(cx, adt_def.did(), None, ret); | |
| }); | |
| } | |
| Some(if adt_def.is_enum() { | |
| let variants: rustc_index::IndexVec<_, _> = adt_def | |
| .variants() | |
| .iter() | |
| .map(|variant| clean_variant_def_with_args(variant, args, cx)) | |
| .collect(); | |
| if !adt_def.did().is_local() { | |
| inline::record_extern_fqn(cx, adt_def.did(), ItemType::Enum); | |
| } | |
| TypeAliasInnerType::Enum { | |
| variants, | |
| is_non_exhaustive: adt_def.is_variant_list_non_exhaustive(), | |
| } | |
| } else { | |
| let variant = adt_def | |
| .variants() | |
| .iter() | |
| .next() | |
| .unwrap_or_else(|| bug!("a struct or union should always have one variant def")); | |
| let fields: Vec<_> = | |
| clean_variant_def_with_args(variant, args, cx).kind.inner_items().cloned().collect(); | |
| if adt_def.is_struct() { | |
| if !adt_def.did().is_local() { | |
| inline::record_extern_fqn(cx, adt_def.did(), ItemType::Struct); | |
| } | |
| TypeAliasInnerType::Struct { ctor_kind: variant.ctor_kind(), fields } | |
| } else { | |
| if !adt_def.did().is_local() { | |
| inline::record_extern_fqn(cx, adt_def.did(), ItemType::Union); | |
| } | |
| TypeAliasInnerType::Union { fields } | |
| } | |
| }) | |
| } | |
| fn clean_proc_macro<'tcx>( | |
| item: &hir::Item<'tcx>, | |
| name: &mut Symbol, | |
| kind: MacroKind, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> ItemKind { | |
| if kind != MacroKind::Derive { | |
| return ProcMacroItem(ProcMacro { kind, helpers: vec![] }); | |
| } | |
| let attrs = cx.tcx.hir_attrs(item.hir_id()); | |
| let Some((trait_name, helper_attrs)) = find_attr!(attrs, ProcMacroDerive { trait_name, helper_attrs, ..} => (*trait_name, helper_attrs)) | |
| else { | |
| return ProcMacroItem(ProcMacro { kind, helpers: vec![] }); | |
| }; | |
| *name = trait_name; | |
| let helpers = helper_attrs.iter().copied().collect(); | |
| ProcMacroItem(ProcMacro { kind, helpers }) | |
| } | |
| 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 = if find_attr!(attrs, ProcMacro(..)) { | |
| Some(MacroKind::Bang) | |
| } else if find_attr!(attrs, ProcMacroDerive { .. }) { | |
| Some(MacroKind::Derive) | |
| } else if find_attr!(attrs, ProcMacroAttribute(..)) { | |
| Some(MacroKind::Attr) | |
| } else { | |
| None | |
| }; | |
| match macro_kind { | |
| Some(kind) => clean_proc_macro(item, name, kind, cx), | |
| None => { | |
| let mut func = clean_function(cx, sig, generics, ParamsSrc::Body(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: &[hir::Attribute]) { | |
| let Some(indexes) = find_attr!(attrs, RustcLegacyConstGenerics{fn_indexes,..} => fn_indexes) | |
| else { | |
| return; | |
| }; | |
| for (pos, (index, _)) in indexes.iter().enumerate() { | |
| let GenericParamDef { name, kind, .. } = func.generics.params.remove(0); | |
| if let GenericParamDefKind::Const { ty, .. } = kind { | |
| func.decl | |
| .inputs | |
| .insert(*index, Parameter { name: Some(name), type_: *ty, is_const: true }); | |
| } else { | |
| panic!("unexpected non const in position {pos}"); | |
| } | |
| } | |
| } | |
| enum ParamsSrc<'tcx> { | |
| Body(hir::BodyId), | |
| Idents(&'tcx [Option<Ident>]), | |
| } | |
| fn clean_function<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| sig: &hir::FnSig<'tcx>, | |
| generics: &hir::Generics<'tcx>, | |
| params: ParamsSrc<'tcx>, | |
| ) -> Box<Function> { | |
| let (generics, decl) = enter_impl_trait(cx, |cx| { | |
| // NOTE: Generics must be cleaned before params. | |
| let generics = clean_generics(generics, cx); | |
| let params = match params { | |
| ParamsSrc::Body(body_id) => clean_params_via_body(cx, sig.decl.inputs, body_id), | |
| // Let's not perpetuate anon params from Rust 2015; use `_` for them. | |
| ParamsSrc::Idents(idents) => clean_params(cx, sig.decl.inputs, idents, |ident| { | |
| Some(ident.map_or(kw::Underscore, |ident| ident.name)) | |
| }), | |
| }; | |
| let decl = clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params); | |
| (generics, decl) | |
| }); | |
| Box::new(Function { decl, generics }) | |
| } | |
| fn clean_params<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| types: &[hir::Ty<'tcx>], | |
| idents: &[Option<Ident>], | |
| postprocess: impl Fn(Option<Ident>) -> Option<Symbol>, | |
| ) -> Vec<Parameter> { | |
| types | |
| .iter() | |
| .enumerate() | |
| .map(|(i, ty)| Parameter { | |
| name: postprocess(idents[i]), | |
| type_: clean_ty(ty, cx), | |
| is_const: false, | |
| }) | |
| .collect() | |
| } | |
| fn clean_params_via_body<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| types: &[hir::Ty<'tcx>], | |
| body_id: hir::BodyId, | |
| ) -> Vec<Parameter> { | |
| types | |
| .iter() | |
| .zip(cx.tcx.hir_body(body_id).params) | |
| .map(|(ty, param)| Parameter { | |
| name: Some(name_from_pat(param.pat)), | |
| type_: clean_ty(ty, cx), | |
| is_const: false, | |
| }) | |
| .collect() | |
| } | |
| fn clean_fn_decl_with_params<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| decl: &hir::FnDecl<'tcx>, | |
| header: Option<&hir::FnHeader>, | |
| params: Vec<Parameter>, | |
| ) -> FnDecl { | |
| let mut output = match decl.output { | |
| hir::FnRetTy::Return(typ) => clean_ty(typ, cx), | |
| hir::FnRetTy::DefaultReturn(..) => Type::Tuple(Vec::new()), | |
| }; | |
| if let Some(header) = header | |
| && header.is_async() | |
| { | |
| output = output.sugared_async_return_type(); | |
| } | |
| FnDecl { inputs: params, output, c_variadic: decl.c_variadic } | |
| } | |
| fn clean_poly_fn_sig<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| did: Option<DefId>, | |
| sig: ty::PolyFnSig<'tcx>, | |
| ) -> FnDecl { | |
| let mut output = clean_middle_ty(sig.output(), cx, None, None); | |
| // If the return type isn't an `impl Trait`, we can safely assume that this | |
| // function isn't async without needing to execute the query `asyncness` at | |
| // all which gives us a noticeable performance boost. | |
| if let Some(did) = did | |
| && let Type::ImplTrait(_) = output | |
| && cx.tcx.asyncness(did).is_async() | |
| { | |
| output = output.sugared_async_return_type(); | |
| } | |
| let mut idents = did.map(|did| cx.tcx.fn_arg_idents(did)).unwrap_or_default().iter().copied(); | |
| // If this comes from a fn item, let's not perpetuate anon params from Rust 2015; use `_` for them. | |
| // If this comes from a fn ptr ty, we just keep params unnamed since it's more conventional stylistically. | |
| // Since the param name is not part of the semantic type, these params never bear a name unlike | |
| // in the HIR case, thus we can't perform any fancy fallback logic unlike `clean_bare_fn_ty`. | |
| let fallback = did.map(|_| kw::Underscore); | |
| let params = sig | |
| .inputs() | |
| .iter() | |
| .map(|ty| Parameter { | |
| name: idents.next().flatten().map(|ident| ident.name).or(fallback), | |
| type_: clean_middle_ty(ty.map_bound(|ty| *ty), cx, None, None), | |
| is_const: false, | |
| }) | |
| .collect(); | |
| FnDecl { inputs: params, output, c_variadic: sig.skip_binder().c_variadic } | |
| } | |
| 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 | |
| } | |
| fn clean_poly_trait_ref<'tcx>( | |
| poly_trait_ref: &hir::PolyTraitRef<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> PolyTrait { | |
| PolyTrait { | |
| trait_: clean_trait_ref(&poly_trait_ref.trait_ref, cx), | |
| generic_params: poly_trait_ref | |
| .bound_generic_params | |
| .iter() | |
| .filter(|p| !is_elided_lifetime(p)) | |
| .map(|x| clean_generic_param(cx, None, x)) | |
| .collect(), | |
| } | |
| } | |
| fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext<'tcx>) -> Item { | |
| let local_did = trait_item.owner_id.to_def_id(); | |
| cx.with_param_env(local_did, |cx| { | |
| let inner = match trait_item.kind { | |
| hir::TraitItemKind::Const(ty, Some(default), _) => { | |
| ProvidedAssocConstItem(Box::new(Constant { | |
| generics: enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)), | |
| kind: clean_const_item_rhs(default, local_did), | |
| type_: clean_ty(ty, cx), | |
| })) | |
| } | |
| hir::TraitItemKind::Const(ty, None, _) => { | |
| let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); | |
| RequiredAssocConstItem(generics, Box::new(clean_ty(ty, cx))) | |
| } | |
| hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => { | |
| let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body)); | |
| MethodItem(m, Defaultness::from_trait_item(trait_item.defaultness)) | |
| } | |
| hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => { | |
| let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Idents(idents)); | |
| RequiredMethodItem(m, Defaultness::from_trait_item(trait_item.defaultness)) | |
| } | |
| hir::TraitItemKind::Type(bounds, Some(default)) => { | |
| let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); | |
| let bounds = bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(); | |
| let item_type = | |
| clean_middle_ty(ty::Binder::dummy(lower_ty(cx.tcx, default)), cx, None, None); | |
| AssocTypeItem( | |
| Box::new(TypeAlias { | |
| type_: clean_ty(default, cx), | |
| generics, | |
| inner_type: None, | |
| item_type: Some(item_type), | |
| }), | |
| bounds, | |
| ) | |
| } | |
| hir::TraitItemKind::Type(bounds, None) => { | |
| let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); | |
| let bounds = bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(); | |
| RequiredAssocTypeItem(generics, bounds) | |
| } | |
| }; | |
| Item::from_def_id_and_parts(local_did, Some(trait_item.ident.name), inner, cx) | |
| }) | |
| } | |
| pub(crate) fn clean_impl_item<'tcx>( | |
| impl_: &hir::ImplItem<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Item { | |
| let local_did = impl_.owner_id.to_def_id(); | |
| cx.with_param_env(local_did, |cx| { | |
| let inner = match impl_.kind { | |
| hir::ImplItemKind::Const(ty, expr) => ImplAssocConstItem(Box::new(Constant { | |
| generics: clean_generics(impl_.generics, cx), | |
| kind: clean_const_item_rhs(expr, local_did), | |
| type_: clean_ty(ty, cx), | |
| })), | |
| hir::ImplItemKind::Fn(ref sig, body) => { | |
| let m = clean_function(cx, sig, impl_.generics, ParamsSrc::Body(body)); | |
| let defaultness = match impl_.impl_kind { | |
| hir::ImplItemImplKind::Inherent { .. } => hir::Defaultness::Final, | |
| hir::ImplItemImplKind::Trait { defaultness, .. } => defaultness, | |
| }; | |
| MethodItem(m, Defaultness::from_impl_item(defaultness)) | |
| } | |
| hir::ImplItemKind::Type(hir_ty) => { | |
| let type_ = clean_ty(hir_ty, cx); | |
| let generics = clean_generics(impl_.generics, cx); | |
| let item_type = | |
| clean_middle_ty(ty::Binder::dummy(lower_ty(cx.tcx, hir_ty)), cx, None, None); | |
| AssocTypeItem( | |
| Box::new(TypeAlias { | |
| type_, | |
| generics, | |
| inner_type: None, | |
| item_type: Some(item_type), | |
| }), | |
| Vec::new(), | |
| ) | |
| } | |
| }; | |
| Item::from_def_id_and_parts(local_did, Some(impl_.ident.name), inner, cx) | |
| }) | |
| } | |
| pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocContext<'_>) -> Item { | |
| let tcx = cx.tcx; | |
| let kind = match assoc_item.kind { | |
| ty::AssocKind::Const { .. } => { | |
| let ty = clean_middle_ty( | |
| ty::Binder::dummy(tcx.type_of(assoc_item.def_id).instantiate_identity()), | |
| cx, | |
| Some(assoc_item.def_id), | |
| None, | |
| ); | |
| let mut generics = clean_ty_generics(cx, assoc_item.def_id); | |
| simplify::move_bounds_to_generic_parameters(&mut generics); | |
| match assoc_item.container { | |
| ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => { | |
| ImplAssocConstItem(Box::new(Constant { | |
| generics, | |
| kind: ConstantKind::Extern { def_id: assoc_item.def_id }, | |
| type_: ty, | |
| })) | |
| } | |
| ty::AssocContainer::Trait => { | |
| if tcx.defaultness(assoc_item.def_id).has_value() { | |
| ProvidedAssocConstItem(Box::new(Constant { | |
| generics, | |
| kind: ConstantKind::Extern { def_id: assoc_item.def_id }, | |
| type_: ty, | |
| })) | |
| } else { | |
| RequiredAssocConstItem(generics, Box::new(ty)) | |
| } | |
| } | |
| } | |
| } | |
| ty::AssocKind::Fn { has_self, .. } => { | |
| let mut item = inline::build_function(cx, assoc_item.def_id); | |
| if has_self { | |
| let self_ty = match assoc_item.container { | |
| ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => { | |
| tcx.type_of(assoc_item.container_id(tcx)).instantiate_identity() | |
| } | |
| ty::AssocContainer::Trait => tcx.types.self_param, | |
| }; | |
| let self_param_ty = | |
| tcx.fn_sig(assoc_item.def_id).instantiate_identity().input(0).skip_binder(); | |
| if self_param_ty == self_ty { | |
| item.decl.inputs[0].type_ = SelfTy; | |
| } else if let ty::Ref(_, ty, _) = *self_param_ty.kind() | |
| && ty == self_ty | |
| { | |
| match item.decl.inputs[0].type_ { | |
| BorrowedRef { ref mut type_, .. } => **type_ = SelfTy, | |
| _ => unreachable!(), | |
| } | |
| } | |
| } | |
| let defaultness = assoc_item.defaultness(tcx); | |
| let (provided, defaultness) = match assoc_item.container { | |
| ty::AssocContainer::Trait => { | |
| (defaultness.has_value(), Defaultness::from_trait_item(defaultness)) | |
| } | |
| ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => { | |
| (true, Defaultness::from_impl_item(defaultness)) | |
| } | |
| }; | |
| if provided { | |
| MethodItem(item, defaultness) | |
| } else { | |
| RequiredMethodItem(item, defaultness) | |
| } | |
| } | |
| ty::AssocKind::Type { .. } => { | |
| let my_name = assoc_item.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 { | |
| ConstantKind::TyConst { expr } => **expr == *param.name.as_str(), | |
| _ => false, | |
| }, | |
| _ => false, | |
| } | |
| } | |
| let mut predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates; | |
| if let ty::AssocContainer::Trait = assoc_item.container { | |
| let bounds = tcx.explicit_item_bounds(assoc_item.def_id).iter_identity_copied(); | |
| predicates = tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied())); | |
| } | |
| let mut generics = clean_ty_generics_inner( | |
| cx, | |
| tcx.generics_of(assoc_item.def_id), | |
| ty::GenericPredicates { parent: None, predicates }, | |
| ); | |
| simplify::move_bounds_to_generic_parameters(&mut generics); | |
| if let ty::AssocContainer::Trait = assoc_item.container { | |
| // Move bounds that are (likely) directly attached to the associated type | |
| // from the where-clause to the associated type. | |
| // There is no guarantee that this is what the user actually wrote but we have | |
| // no way of knowing. | |
| let mut bounds: Vec<GenericBound> = Vec::new(); | |
| generics.where_predicates.retain_mut(|pred| match *pred { | |
| WherePredicate::BoundPredicate { | |
| ty: | |
| QPath(box QPathData { | |
| ref assoc, | |
| ref self_type, | |
| trait_: Some(ref trait_), | |
| .. | |
| }), | |
| bounds: ref mut pred_bounds, | |
| .. | |
| } => { | |
| if assoc.name != my_name { | |
| return true; | |
| } | |
| if trait_.def_id() != assoc_item.container_id(tcx) { | |
| return true; | |
| } | |
| if *self_type != SelfTy { | |
| return true; | |
| } | |
| match &assoc.args { | |
| GenericArgs::AngleBracketed { args, constraints } => { | |
| if !constraints.is_empty() | |
| || generics | |
| .params | |
| .iter() | |
| .zip(args.iter()) | |
| .any(|(param, arg)| !param_eq_arg(param, arg)) | |
| { | |
| return true; | |
| } | |
| } | |
| 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. | |
| } | |
| GenericArgs::ReturnTypeNotation => { | |
| // Never move these. | |
| } | |
| } | |
| bounds.extend(mem::take(pred_bounds)); | |
| false | |
| } | |
| _ => true, | |
| }); | |
| bounds.retain(|b| { | |
| // FIXME(sized-hierarchy): Always skip `MetaSized` bounds so that only `?Sized` | |
| // is shown and none of the new sizedness traits leak into documentation. | |
| !b.is_meta_sized_bound(cx) | |
| }); | |
| // 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.defaultness(assoc_item.def_id).has_value() { | |
| AssocTypeItem( | |
| Box::new(TypeAlias { | |
| type_: clean_middle_ty( | |
| ty::Binder::dummy( | |
| tcx.type_of(assoc_item.def_id).instantiate_identity(), | |
| ), | |
| cx, | |
| Some(assoc_item.def_id), | |
| None, | |
| ), | |
| generics, | |
| inner_type: None, | |
| item_type: None, | |
| }), | |
| bounds, | |
| ) | |
| } else { | |
| RequiredAssocTypeItem(generics, bounds) | |
| } | |
| } else { | |
| AssocTypeItem( | |
| Box::new(TypeAlias { | |
| type_: clean_middle_ty( | |
| ty::Binder::dummy( | |
| tcx.type_of(assoc_item.def_id).instantiate_identity(), | |
| ), | |
| cx, | |
| Some(assoc_item.def_id), | |
| None, | |
| ), | |
| generics, | |
| inner_type: None, | |
| item_type: None, | |
| }), | |
| // Associated types inside trait or inherent impls are not allowed to have | |
| // item bounds. Thus we don't attempt to move any bounds there. | |
| Vec::new(), | |
| ) | |
| } | |
| } | |
| }; | |
| Item::from_def_id_and_parts(assoc_item.def_id, Some(assoc_item.name()), kind, cx) | |
| } | |
| fn first_non_private_clean_path<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| path: &hir::Path<'tcx>, | |
| new_path_segments: &'tcx [hir::PathSegment<'tcx>], | |
| new_path_span: rustc_span::Span, | |
| ) -> Path { | |
| let new_hir_path = | |
| hir::Path { segments: new_path_segments, res: path.res, span: new_path_span }; | |
| let mut new_clean_path = clean_path(&new_hir_path, cx); | |
| // In here we need to play with the path data one last time to provide it the | |
| // missing `args` and `res` of the final `Path` we get, which, since it comes | |
| // from a re-export, doesn't have the generics that were originally there, so | |
| // we add them by hand. | |
| if let Some(path_last) = path.segments.last().as_ref() | |
| && let Some(new_path_last) = new_clean_path.segments[..].last_mut() | |
| && let Some(path_last_args) = path_last.args.as_ref() | |
| && path_last.args.is_some() | |
| { | |
| assert!(new_path_last.args.is_empty()); | |
| new_path_last.args = clean_generic_args(None, path_last_args, cx); | |
| } | |
| new_clean_path | |
| } | |
| /// The goal of this function is to return the first `Path` which is not private (ie not private | |
| /// or `doc(hidden)`). If it's not possible, it'll return the "end type". | |
| /// | |
| /// If the path is not a re-export or is public, it'll return `None`. | |
| fn first_non_private<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| hir_id: hir::HirId, | |
| path: &hir::Path<'tcx>, | |
| ) -> Option<Path> { | |
| let target_def_id = path.res.opt_def_id()?; | |
| let (parent_def_id, ident) = match &path.segments { | |
| [] => return None, | |
| // Relative paths are available in the same scope as the owner. | |
| [leaf] => (cx.tcx.local_parent(hir_id.owner.def_id), leaf.ident), | |
| // So are self paths. | |
| [parent, leaf] if parent.ident.name == kw::SelfLower => { | |
| (cx.tcx.local_parent(hir_id.owner.def_id), leaf.ident) | |
| } | |
| // Crate paths are not. We start from the crate root. | |
| [parent, leaf] if matches!(parent.ident.name, kw::Crate | kw::PathRoot) => { | |
| (LOCAL_CRATE.as_def_id().as_local()?, leaf.ident) | |
| } | |
| [parent, leaf] if parent.ident.name == kw::Super => { | |
| let parent_mod = cx.tcx.parent_module(hir_id); | |
| if let Some(super_parent) = cx.tcx.opt_local_parent(parent_mod.to_local_def_id()) { | |
| (super_parent, leaf.ident) | |
| } else { | |
| // If we can't find the parent of the parent, then the parent is already the crate. | |
| (LOCAL_CRATE.as_def_id().as_local()?, leaf.ident) | |
| } | |
| } | |
| // Absolute paths are not. We start from the parent of the item. | |
| [.., parent, leaf] => (parent.res.opt_def_id()?.as_local()?, leaf.ident), | |
| }; | |
| // First we try to get the `DefId` of the item. | |
| for child in | |
| cx.tcx.module_children_local(parent_def_id).iter().filter(move |c| c.ident == ident) | |
| { | |
| if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = child.res { | |
| continue; | |
| } | |
| if let Some(def_id) = child.res.opt_def_id() | |
| && target_def_id == def_id | |
| { | |
| let mut last_path_res = None; | |
| 'reexps: for reexp in child.reexport_chain.iter() { | |
| if let Some(use_def_id) = reexp.id() | |
| && let Some(local_use_def_id) = use_def_id.as_local() | |
| && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_use_def_id) | |
| && let hir::ItemKind::Use(path, hir::UseKind::Single(_)) = item.kind | |
| { | |
| for res in path.res.present_items() { | |
| if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res { | |
| continue; | |
| } | |
| if (cx.document_hidden() || | |
| !cx.tcx.is_doc_hidden(use_def_id)) && | |
| // We never check for "cx.document_private()" | |
| // because if a re-export is not fully public, it's never | |
| // documented. | |
| cx.tcx.local_visibility(local_use_def_id).is_public() | |
| { | |
| break 'reexps; | |
| } | |
| last_path_res = Some((path, res)); | |
| continue 'reexps; | |
| } | |
| } | |
| } | |
| if !child.reexport_chain.is_empty() { | |
| // So in here, we use the data we gathered from iterating the reexports. If | |
| // `last_path_res` is set, it can mean two things: | |
| // | |
| // 1. We found a public reexport. | |
| // 2. We didn't find a public reexport so it's the "end type" path. | |
| if let Some((new_path, _)) = last_path_res { | |
| return Some(first_non_private_clean_path( | |
| cx, | |
| path, | |
| new_path.segments, | |
| new_path.span, | |
| )); | |
| } | |
| // If `last_path_res` is `None`, it can mean two things: | |
| // | |
| // 1. The re-export is public, no need to change anything, just use the path as is. | |
| // 2. Nothing was found, so let's just return the original path. | |
| return None; | |
| } | |
| } | |
| } | |
| None | |
| } | |
| 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.args.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 { | |
| // First we check if it's a private re-export. | |
| let path = if let Some(path) = first_non_private(cx, hir_id, path) { | |
| path | |
| } else { | |
| 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 = lower_ty(cx.tcx, hir_ty); | |
| // `hir_to_ty` can return projection types with escaping vars for GATs, e.g. `<() as Trait>::Gat<'_>` | |
| if !ty.has_escaping_bound_vars() | |
| && let Some(normalized_value) = normalize(cx, ty::Binder::dummy(ty)) | |
| { | |
| return clean_middle_ty(normalized_value, cx, None, None); | |
| } | |
| let trait_segments = &p.segments[..p.segments.len() - 1]; | |
| let trait_def = cx.tcx.parent(p.res.def_id()); | |
| let trait_ = self::Path { | |
| res: Res::Def(DefKind::Trait, trait_def), | |
| segments: trait_segments.iter().map(|x| clean_path_segment(x, cx)).collect(), | |
| }; | |
| register_res(cx, trait_.res); | |
| let self_def_id = DefId::local(qself.hir_id.owner.def_id.local_def_index); | |
| let self_type = clean_ty(qself, cx); | |
| let should_fully_qualify = | |
| should_fully_qualify_path(Some(self_def_id), &trait_, &self_type); | |
| Type::QPath(Box::new(QPathData { | |
| assoc: clean_path_segment(p.segments.last().expect("segments were empty"), cx), | |
| should_fully_qualify, | |
| self_type, | |
| trait_: Some(trait_), | |
| })) | |
| } | |
| hir::QPath::TypeRelative(qself, segment) => { | |
| let ty = lower_ty(cx.tcx, hir_ty); | |
| let self_type = clean_ty(qself, cx); | |
| let (trait_, should_fully_qualify) = match ty.kind() { | |
| ty::Alias(ty::Projection, proj) => { | |
| let res = Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id); | |
| let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx); | |
| register_res(cx, trait_.res); | |
| let self_def_id = res.opt_def_id(); | |
| let should_fully_qualify = | |
| should_fully_qualify_path(self_def_id, &trait_, &self_type); | |
| (Some(trait_), should_fully_qualify) | |
| } | |
| ty::Alias(ty::Inherent, _) => (None, false), | |
| // Rustdoc handles `ty::Error`s by turning them into `Type::Infer`s. | |
| ty::Error(_) => return Type::Infer, | |
| _ => bug!("clean: expected associated type, found `{ty:?}`"), | |
| }; | |
| Type::QPath(Box::new(QPathData { | |
| assoc: clean_path_segment(segment, cx), | |
| should_fully_qualify, | |
| self_type, | |
| trait_, | |
| })) | |
| } | |
| } | |
| } | |
| 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.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id()) | |
| && !cx.current_type_aliases.contains_key(&def_id.to_def_id()) | |
| { | |
| &cx.tcx.hir_expect_item(def_id).kind | |
| } else { | |
| return None; | |
| }; | |
| let hir::ItemKind::TyAlias(_, generics, ty) = alias else { return None }; | |
| let final_seg = &path.segments.last().expect("segments were empty"); | |
| let mut args = DefIdMap::default(); | |
| let generic_args = final_seg.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 { | |
| let lt = if !lt.is_anonymous() { | |
| clean_lifetime(lt, cx) | |
| } else { | |
| Lifetime::elided() | |
| }; | |
| args.insert(param.def_id.to_def_id(), GenericArg::Lifetime(lt)); | |
| } | |
| indices.lifetimes += 1; | |
| } | |
| hir::GenericParamKind::Type { ref default, .. } => { | |
| 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.as_unambig_ty()); | |
| } | |
| j += 1; | |
| None | |
| } | |
| _ => None, | |
| }); | |
| if let Some(ty) = type_.or(*default) { | |
| args.insert(param.def_id.to_def_id(), GenericArg::Type(clean_ty(ty, cx))); | |
| } | |
| indices.types += 1; | |
| } | |
| // FIXME(#82852): Instantiate const parameters. | |
| hir::GenericParamKind::Const { .. } => {} | |
| } | |
| } | |
| Some(cx.enter_alias(args, def_id.to_def_id(), |cx| { | |
| cx.with_param_env(def_id.to_def_id(), |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::Ref(l, ref m) => { | |
| let lifetime = if l.is_anonymous() { 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::Pat(ty, pat) => Type::Pat(Box::new(clean_ty(ty, cx)), format!("{pat:?}").into()), | |
| TyKind::FieldOf(ty, hir::TyFieldPath { variant, field }) => { | |
| let field_str = if let Some(variant) = variant { | |
| format!("{variant}.{field}") | |
| } else { | |
| format!("{field}") | |
| }; | |
| Type::FieldOf(Box::new(clean_ty(ty, cx)), field_str.into()) | |
| } | |
| TyKind::Array(ty, const_arg) => { | |
| // 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 first substitute generic parameters which | |
| // results in an ICE while manually constructing the constant and using `eval` | |
| // does nothing for `ConstKind::Param`. | |
| let length = match const_arg.kind { | |
| hir::ConstArgKind::Infer(..) | hir::ConstArgKind::Error(..) => "_".to_string(), | |
| hir::ConstArgKind::Anon(hir::AnonConst { def_id, .. }) => { | |
| let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, cx.tcx.types.usize); | |
| let typing_env = ty::TypingEnv::post_analysis(cx.tcx, *def_id); | |
| let ct = cx.tcx.normalize_erasing_regions(typing_env, ct); | |
| print_const(cx, ct) | |
| } | |
| hir::ConstArgKind::Struct(..) | |
| | hir::ConstArgKind::Path(..) | |
| | hir::ConstArgKind::TupleCall(..) | |
| | hir::ConstArgKind::Tup(..) | |
| | hir::ConstArgKind::Array(..) | |
| | hir::ConstArgKind::Literal { .. } => { | |
| let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, cx.tcx.types.usize); | |
| print_const(cx, ct) | |
| } | |
| }; | |
| Array(Box::new(clean_ty(ty, cx)), length.into()) | |
| } | |
| TyKind::Tup(tys) => Tuple(tys.iter().map(|ty| clean_ty(ty, cx)).collect()), | |
| TyKind::OpaqueDef(ty) => { | |
| ImplTrait(ty.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect()) | |
| } | |
| TyKind::Path(_) => clean_qpath(ty, cx), | |
| TyKind::TraitObject(bounds, lifetime) => { | |
| let bounds = bounds.iter().map(|bound| clean_poly_trait_ref(bound, cx)).collect(); | |
| let lifetime = if !lifetime.is_elided() { | |
| Some(clean_lifetime(lifetime.pointer(), cx)) | |
| } else { | |
| None | |
| }; | |
| DynTrait(bounds, lifetime) | |
| } | |
| TyKind::FnPtr(barefn) => BareFunction(Box::new(clean_bare_fn_ty(barefn, cx))), | |
| TyKind::UnsafeBinder(unsafe_binder_ty) => { | |
| UnsafeBinder(Box::new(clean_unsafe_binder_ty(unsafe_binder_ty, cx))) | |
| } | |
| // Rustdoc handles `TyKind::Err`s by turning them into `Type::Infer`s. | |
| TyKind::Infer(()) | |
| | TyKind::Err(_) | |
| | TyKind::InferDelegation(..) | |
| | TyKind::TraitAscription(_) => Infer, | |
| } | |
| } | |
| /// Returns `None` if the type could not be normalized | |
| fn normalize<'tcx>( | |
| cx: &DocContext<'tcx>, | |
| ty: ty::Binder<'tcx, Ty<'tcx>>, | |
| ) -> Option<ty::Binder<'tcx, 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 rustc_middle::traits::ObligationCause; | |
| use rustc_trait_selection::infer::TyCtxtInferExt; | |
| use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; | |
| // Try to normalize `<X as Y>::T` to a type | |
| let infcx = cx.tcx.infer_ctxt().build(TypingMode::non_body_analysis()); | |
| let normalized = infcx | |
| .at(&ObligationCause::dummy(), cx.param_env) | |
| .query_normalize(ty) | |
| .map(|resolved| infcx.resolve_vars_if_possible(resolved.value)); | |
| match normalized { | |
| Ok(normalized_value) => { | |
| debug!("normalized {ty:?} to {normalized_value:?}"); | |
| Some(normalized_value) | |
| } | |
| Err(err) => { | |
| debug!("failed to normalize {ty:?}: {err:?}"); | |
| None | |
| } | |
| } | |
| } | |
| fn clean_trait_object_lifetime_bound<'tcx>( | |
| region: ty::Region<'tcx>, | |
| container: Option<ContainerTy<'_, 'tcx>>, | |
| preds: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | |
| tcx: TyCtxt<'tcx>, | |
| ) -> Option<Lifetime> { | |
| if can_elide_trait_object_lifetime_bound(region, container, preds, tcx) { | |
| return None; | |
| } | |
| // Since there is a semantic difference between an implicitly elided (i.e. "defaulted") object | |
| // lifetime and an explicitly elided object lifetime (`'_`), we intentionally don't hide the | |
| // latter contrary to `clean_middle_region`. | |
| match region.kind() { | |
| ty::ReStatic => Some(Lifetime::statik()), | |
| ty::ReEarlyParam(region) => Some(Lifetime(region.name)), | |
| ty::ReBound(_, ty::BoundRegion { kind: ty::BoundRegionKind::Named(def_id), .. }) => { | |
| Some(Lifetime(tcx.item_name(def_id))) | |
| } | |
| ty::ReBound(..) | |
| | ty::ReLateParam(_) | |
| | ty::ReVar(_) | |
| | ty::RePlaceholder(_) | |
| | ty::ReErased | |
| | ty::ReError(_) => None, | |
| } | |
| } | |
| fn can_elide_trait_object_lifetime_bound<'tcx>( | |
| region: ty::Region<'tcx>, | |
| container: Option<ContainerTy<'_, 'tcx>>, | |
| preds: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | |
| tcx: TyCtxt<'tcx>, | |
| ) -> bool { | |
| // Below we quote extracts from https://doc.rust-lang.org/stable/reference/lifetime-elision.html#default-trait-object-lifetimes | |
| // > If the trait object is used as a type argument of a generic type then the containing type is | |
| // > first used to try to infer a bound. | |
| let default = container | |
| .map_or(ObjectLifetimeDefault::Empty, |container| container.object_lifetime_default(tcx)); | |
| // > If there is a unique bound from the containing type then that is the default | |
| // If there is a default object lifetime and the given region is lexically equal to it, elide it. | |
| match default { | |
| ObjectLifetimeDefault::Static => return region.kind() == ty::ReStatic, | |
| // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly. | |
| ObjectLifetimeDefault::Arg(default) => { | |
| return region.get_name(tcx) == default.get_name(tcx); | |
| } | |
| // > If there is more than one bound from the containing type then an explicit bound must be specified | |
| // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible. | |
| // Don't elide the lifetime. | |
| ObjectLifetimeDefault::Ambiguous => return false, | |
| // There is no meaningful bound. Further processing is needed... | |
| ObjectLifetimeDefault::Empty => {} | |
| } | |
| // > If neither of those rules apply, then the bounds on the trait are used: | |
| match *object_region_bounds(tcx, preds) { | |
| // > If the trait has no lifetime bounds, then the lifetime is inferred in expressions | |
| // > and is 'static outside of expressions. | |
| // FIXME: If we are in an expression context (i.e. fn bodies and const exprs) then the default is | |
| // `'_` and not `'static`. Only if we are in a non-expression one, the default is `'static`. | |
| // Note however that at the time of this writing it should be fine to disregard this subtlety | |
| // as we neither render const exprs faithfully anyway (hiding them in some places or using `_` instead) | |
| // nor show the contents of fn bodies. | |
| [] => region.kind() == ty::ReStatic, | |
| // > If the trait is defined with a single lifetime bound then that bound is used. | |
| // > If 'static is used for any lifetime bound then 'static is used. | |
| // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly. | |
| [object_region] => object_region.get_name(tcx) == region.get_name(tcx), | |
| // There are several distinct trait regions and none are `'static`. | |
| // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible. | |
| // Don't elide the lifetime. | |
| _ => false, | |
| } | |
| } | |
| #[derive(Debug)] | |
| pub(crate) enum ContainerTy<'a, 'tcx> { | |
| Ref(ty::Region<'tcx>), | |
| Regular { | |
| ty: DefId, | |
| /// The arguments *have* to contain an arg for the self type if the corresponding generics | |
| /// contain a self type. | |
| args: ty::Binder<'tcx, &'a [ty::GenericArg<'tcx>]>, | |
| arg: usize, | |
| }, | |
| } | |
| impl<'tcx> ContainerTy<'_, 'tcx> { | |
| fn object_lifetime_default(self, tcx: TyCtxt<'tcx>) -> ObjectLifetimeDefault<'tcx> { | |
| match self { | |
| Self::Ref(region) => ObjectLifetimeDefault::Arg(region), | |
| Self::Regular { ty: container, args, arg: index } => { | |
| let (DefKind::Struct | |
| | DefKind::Union | |
| | DefKind::Enum | |
| | DefKind::TyAlias | |
| | DefKind::Trait) = tcx.def_kind(container) | |
| else { | |
| return ObjectLifetimeDefault::Empty; | |
| }; | |
| let generics = tcx.generics_of(container); | |
| debug_assert_eq!(generics.parent_count, 0); | |
| let param = generics.own_params[index].def_id; | |
| let default = tcx.object_lifetime_default(param); | |
| match default { | |
| rbv::ObjectLifetimeDefault::Param(lifetime) => { | |
| // The index is relative to the parent generics but since we don't have any, | |
| // we don't need to translate it. | |
| let index = generics.param_def_id_to_index[&lifetime]; | |
| let arg = args.skip_binder()[index as usize].expect_region(); | |
| ObjectLifetimeDefault::Arg(arg) | |
| } | |
| rbv::ObjectLifetimeDefault::Empty => ObjectLifetimeDefault::Empty, | |
| rbv::ObjectLifetimeDefault::Static => ObjectLifetimeDefault::Static, | |
| rbv::ObjectLifetimeDefault::Ambiguous => ObjectLifetimeDefault::Ambiguous, | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #[derive(Debug, Clone, Copy)] | |
| pub(crate) enum ObjectLifetimeDefault<'tcx> { | |
| Empty, | |
| Static, | |
| Ambiguous, | |
| Arg(ty::Region<'tcx>), | |
| } | |
| #[instrument(level = "trace", skip(cx), ret)] | |
| pub(crate) fn clean_middle_ty<'tcx>( | |
| bound_ty: ty::Binder<'tcx, Ty<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| parent_def_id: Option<DefId>, | |
| container: Option<ContainerTy<'_, 'tcx>>, | |
| ) -> Type { | |
| let bound_ty = normalize(cx, bound_ty).unwrap_or(bound_ty); | |
| match *bound_ty.skip_binder().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(bound_ty.rebind(ty), cx, None, None))), | |
| ty::Pat(ty, pat) => Type::Pat( | |
| Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)), | |
| format!("{pat:?}").into_boxed_str(), | |
| ), | |
| ty::Array(ty, n) => { | |
| let n = cx.tcx.normalize_erasing_regions(cx.typing_env(), n); | |
| let n = print_const(cx, n); | |
| Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)), n.into()) | |
| } | |
| ty::RawPtr(ty, mutbl) => { | |
| RawPointer(mutbl, Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None))) | |
| } | |
| ty::Ref(r, ty, mutbl) => BorrowedRef { | |
| lifetime: clean_middle_region(r, cx), | |
| mutability: mutbl, | |
| type_: Box::new(clean_middle_ty( | |
| bound_ty.rebind(ty), | |
| cx, | |
| None, | |
| Some(ContainerTy::Ref(r)), | |
| )), | |
| }, | |
| ty::FnDef(..) | ty::FnPtr(..) => { | |
| // FIXME: should we merge the outer and inner binders somehow? | |
| let sig = bound_ty.skip_binder().fn_sig(cx.tcx); | |
| let decl = clean_poly_fn_sig(cx, None, sig); | |
| let generic_params = clean_bound_vars(sig.bound_vars(), cx); | |
| BareFunction(Box::new(BareFunctionDecl { | |
| safety: sig.safety(), | |
| generic_params, | |
| decl, | |
| abi: sig.abi(), | |
| })) | |
| } | |
| ty::UnsafeBinder(inner) => { | |
| let generic_params = clean_bound_vars(inner.bound_vars(), cx); | |
| let ty = clean_middle_ty(inner.into(), cx, None, None); | |
| UnsafeBinder(Box::new(UnsafeBinderTy { generic_params, ty })) | |
| } | |
| ty::Adt(def, args) => { | |
| 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 = clean_middle_path(cx, did, false, ThinVec::new(), bound_ty.rebind(args)); | |
| Type::Path { path } | |
| } | |
| ty::Foreign(did) => { | |
| inline::record_extern_fqn(cx, did, ItemType::ForeignType); | |
| let path = clean_middle_path( | |
| cx, | |
| did, | |
| false, | |
| ThinVec::new(), | |
| ty::Binder::dummy(ty::GenericArgs::empty()), | |
| ); | |
| Type::Path { path } | |
| } | |
| ty::Dynamic(obj, 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.auto_traits(); | |
| let did = obj | |
| .principal_def_id() | |
| .or_else(|| dids.next()) | |
| .unwrap_or_else(|| panic!("found trait object `{bound_ty:?}` with no traits?")); | |
| let args = match obj.principal() { | |
| Some(principal) => principal.map_bound(|p| p.args), | |
| // marker traits have no args. | |
| _ => ty::Binder::dummy(ty::GenericArgs::empty()), | |
| }; | |
| inline::record_extern_fqn(cx, did, ItemType::Trait); | |
| let lifetime = clean_trait_object_lifetime_bound(reg, container, obj, cx.tcx); | |
| let mut bounds = dids | |
| .map(|did| { | |
| let empty = ty::Binder::dummy(ty::GenericArgs::empty()); | |
| let path = clean_middle_path(cx, did, false, ThinVec::new(), empty); | |
| inline::record_extern_fqn(cx, did, ItemType::Trait); | |
| PolyTrait { trait_: path, generic_params: Vec::new() } | |
| }) | |
| .collect::<Vec<_>>(); | |
| let constraints = obj | |
| .projection_bounds() | |
| .map(|pb| AssocItemConstraint { | |
| assoc: projection_to_path_segment( | |
| pb.map_bound(|pb| { | |
| pb.with_self_ty(cx.tcx, cx.tcx.types.trait_object_dummy_self) | |
| .projection_term | |
| }), | |
| cx, | |
| ), | |
| kind: AssocItemConstraintKind::Equality { | |
| term: clean_middle_term(pb.map_bound(|pb| pb.term), cx), | |
| }, | |
| }) | |
| .collect(); | |
| let late_bound_regions: FxIndexSet<_> = obj | |
| .iter() | |
| .flat_map(|pred| pred.bound_vars()) | |
| .filter_map(|var| match var { | |
| ty::BoundVariableKind::Region(ty::BoundRegionKind::Named(def_id)) => { | |
| let name = cx.tcx.item_name(def_id); | |
| if name != kw::UnderscoreLifetime { | |
| Some(GenericParamDef::lifetime(def_id, name)) | |
| } else { | |
| None | |
| } | |
| } | |
| _ => None, | |
| }) | |
| .collect(); | |
| let late_bound_regions = late_bound_regions.into_iter().collect(); | |
| let path = clean_middle_path(cx, did, false, constraints, args); | |
| bounds.insert(0, PolyTrait { trait_: path, generic_params: late_bound_regions }); | |
| DynTrait(bounds, lifetime) | |
| } | |
| ty::Tuple(t) => { | |
| Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None, None)).collect()) | |
| } | |
| ty::Alias(ty::Projection, alias_ty @ ty::AliasTy { def_id, args, .. }) => { | |
| if cx.tcx.is_impl_trait_in_trait(def_id) { | |
| clean_middle_opaque_bounds(cx, def_id, args) | |
| } else { | |
| Type::QPath(Box::new(clean_projection( | |
| bound_ty.rebind(alias_ty.into()), | |
| cx, | |
| parent_def_id, | |
| ))) | |
| } | |
| } | |
| ty::Alias(ty::Inherent, alias_ty @ ty::AliasTy { def_id, .. }) => { | |
| let alias_ty = bound_ty.rebind(alias_ty); | |
| let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None); | |
| Type::QPath(Box::new(QPathData { | |
| assoc: PathSegment { | |
| name: cx.tcx.item_name(def_id), | |
| args: GenericArgs::AngleBracketed { | |
| args: clean_middle_generic_args( | |
| cx, | |
| alias_ty.map_bound(|ty| ty.args.as_slice()), | |
| true, | |
| def_id, | |
| ), | |
| constraints: Default::default(), | |
| }, | |
| }, | |
| should_fully_qualify: false, | |
| self_type, | |
| trait_: None, | |
| })) | |
| } | |
| ty::Alias(ty::Free, ty::AliasTy { def_id, args, .. }) => { | |
| if cx.tcx.features().lazy_type_alias() { | |
| // Free type alias `data` represents the `type X` in `type X = Y`. If we need `Y`, | |
| // we need to use `type_of`. | |
| let path = | |
| clean_middle_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(args)); | |
| Type::Path { path } | |
| } else { | |
| let ty = cx.tcx.type_of(def_id).instantiate(cx.tcx, args); | |
| clean_middle_ty(bound_ty.rebind(ty), cx, None, None) | |
| } | |
| } | |
| ty::Param(ref p) => { | |
| if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) { | |
| ImplTrait(bounds) | |
| } else if p.name == kw::SelfUpper { | |
| SelfTy | |
| } else { | |
| Generic(p.name) | |
| } | |
| } | |
| ty::Bound(_, ref ty) => match ty.kind { | |
| ty::BoundTyKind::Param(def_id) => Generic(cx.tcx.item_name(def_id)), | |
| ty::BoundTyKind::Anon => panic!("unexpected anonymous bound type variable"), | |
| }, | |
| ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { | |
| // If it's already in the same alias, don't get an infinite loop. | |
| if cx.current_type_aliases.contains_key(&def_id) { | |
| let path = | |
| clean_middle_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(args)); | |
| Type::Path { path } | |
| } else { | |
| *cx.current_type_aliases.entry(def_id).or_insert(0) += 1; | |
| // Grab the "TraitA + TraitB" from `impl TraitA + TraitB`, | |
| // by looking up the bounds associated with the def_id. | |
| let ty = clean_middle_opaque_bounds(cx, def_id, args); | |
| if let Some(count) = cx.current_type_aliases.get_mut(&def_id) { | |
| *count -= 1; | |
| if *count == 0 { | |
| cx.current_type_aliases.remove(&def_id); | |
| } | |
| } | |
| ty | |
| } | |
| } | |
| ty::Closure(..) => panic!("Closure"), | |
| ty::CoroutineClosure(..) => panic!("CoroutineClosure"), | |
| ty::Coroutine(..) => panic!("Coroutine"), | |
| ty::Placeholder(..) => panic!("Placeholder"), | |
| ty::CoroutineWitness(..) => panic!("CoroutineWitness"), | |
| ty::Infer(..) => panic!("Infer"), | |
| ty::Error(_) => FatalError.raise(), | |
| } | |
| } | |
| fn clean_middle_opaque_bounds<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| impl_trait_def_id: DefId, | |
| args: ty::GenericArgsRef<'tcx>, | |
| ) -> Type { | |
| let mut has_sized = false; | |
| let bounds: Vec<_> = cx | |
| .tcx | |
| .explicit_item_bounds(impl_trait_def_id) | |
| .iter_instantiated_copied(cx.tcx, args) | |
| .collect(); | |
| let mut bounds = bounds | |
| .iter() | |
| .filter_map(|(bound, _)| { | |
| let bound_predicate = bound.kind(); | |
| let trait_ref = match bound_predicate.skip_binder() { | |
| ty::ClauseKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref), | |
| ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => { | |
| return clean_middle_region(reg, cx).map(GenericBound::Outlives); | |
| } | |
| _ => return None, | |
| }; | |
| // FIXME(sized-hierarchy): Always skip `MetaSized` bounds so that only `?Sized` | |
| // is shown and none of the new sizedness traits leak into documentation. | |
| if cx.tcx.is_lang_item(trait_ref.def_id(), LangItem::MetaSized) { | |
| return None; | |
| } | |
| if let Some(sized) = cx.tcx.lang_items().sized_trait() | |
| && trait_ref.def_id() == sized | |
| { | |
| has_sized = true; | |
| return None; | |
| } | |
| let bindings: ThinVec<_> = bounds | |
| .iter() | |
| .filter_map(|(bound, _)| { | |
| let bound = bound.kind(); | |
| if let ty::ClauseKind::Projection(proj_pred) = bound.skip_binder() | |
| && proj_pred.projection_term.trait_ref(cx.tcx) == trait_ref.skip_binder() | |
| { | |
| return Some(AssocItemConstraint { | |
| assoc: projection_to_path_segment( | |
| bound.rebind(proj_pred.projection_term), | |
| cx, | |
| ), | |
| kind: AssocItemConstraintKind::Equality { | |
| term: clean_middle_term(bound.rebind(proj_pred.term), cx), | |
| }, | |
| }); | |
| } | |
| None | |
| }) | |
| .collect(); | |
| Some(clean_poly_trait_ref_with_constraints(cx, trait_ref, bindings)) | |
| }) | |
| .collect::<Vec<_>>(); | |
| if !has_sized { | |
| bounds.push(GenericBound::maybe_sized(cx)); | |
| } | |
| // Move trait bounds to the front. | |
| bounds.sort_by_key(|b| !b.is_trait_bound()); | |
| // Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`). | |
| // Since all potential trait bounds are at the front we can just check the first bound. | |
| if bounds.first().is_none_or(|b| !b.is_trait_bound()) { | |
| bounds.insert(0, GenericBound::sized(cx)); | |
| } | |
| if let Some(args) = cx.tcx.rendered_precise_capturing_args(impl_trait_def_id) { | |
| bounds.push(GenericBound::Use( | |
| args.iter() | |
| .map(|arg| match arg { | |
| hir::PreciseCapturingArgKind::Lifetime(lt) => { | |
| PreciseCapturingArg::Lifetime(Lifetime(*lt)) | |
| } | |
| hir::PreciseCapturingArgKind::Param(param) => { | |
| PreciseCapturingArg::Param(*param) | |
| } | |
| }) | |
| .collect(), | |
| )); | |
| } | |
| ImplTrait(bounds) | |
| } | |
| pub(crate) fn clean_field<'tcx>(field: &hir::FieldDef<'tcx>, cx: &mut DocContext<'tcx>) -> Item { | |
| clean_field_with_def_id(field.def_id.to_def_id(), field.ident.name, clean_ty(field.ty, cx), cx) | |
| } | |
| pub(crate) fn clean_middle_field(field: &ty::FieldDef, cx: &mut DocContext<'_>) -> Item { | |
| clean_field_with_def_id( | |
| field.did, | |
| field.name, | |
| clean_middle_ty( | |
| ty::Binder::dummy(cx.tcx.type_of(field.did).instantiate_identity()), | |
| cx, | |
| Some(field.did), | |
| None, | |
| ), | |
| cx, | |
| ) | |
| } | |
| pub(crate) fn clean_field_with_def_id( | |
| def_id: DefId, | |
| name: Symbol, | |
| ty: Type, | |
| cx: &mut DocContext<'_>, | |
| ) -> Item { | |
| Item::from_def_id_and_parts(def_id, Some(name), StructFieldItem(ty), cx) | |
| } | |
| pub(crate) fn clean_variant_def(variant: &ty::VariantDef, cx: &mut DocContext<'_>) -> Item { | |
| let discriminant = match variant.discr { | |
| ty::VariantDiscr::Explicit(def_id) => Some(Discriminant { expr: None, value: def_id }), | |
| ty::VariantDiscr::Relative(_) => None, | |
| }; | |
| let kind = match variant.ctor_kind() { | |
| Some(CtorKind::Const) => VariantKind::CLike, | |
| Some(CtorKind::Fn) => VariantKind::Tuple( | |
| variant.fields.iter().map(|field| clean_middle_field(field, cx)).collect(), | |
| ), | |
| None => VariantKind::Struct(VariantStruct { | |
| fields: variant.fields.iter().map(|field| clean_middle_field(field, cx)).collect(), | |
| }), | |
| }; | |
| Item::from_def_id_and_parts( | |
| variant.def_id, | |
| Some(variant.name), | |
| VariantItem(Variant { kind, discriminant }), | |
| cx, | |
| ) | |
| } | |
| pub(crate) fn clean_variant_def_with_args<'tcx>( | |
| variant: &ty::VariantDef, | |
| args: &GenericArgsRef<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Item { | |
| let discriminant = match variant.discr { | |
| ty::VariantDiscr::Explicit(def_id) => Some(Discriminant { expr: None, value: def_id }), | |
| ty::VariantDiscr::Relative(_) => None, | |
| }; | |
| use rustc_middle::traits::ObligationCause; | |
| use rustc_trait_selection::infer::TyCtxtInferExt; | |
| use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; | |
| let infcx = cx.tcx.infer_ctxt().build(TypingMode::non_body_analysis()); | |
| let kind = match variant.ctor_kind() { | |
| Some(CtorKind::Const) => VariantKind::CLike, | |
| Some(CtorKind::Fn) => VariantKind::Tuple( | |
| variant | |
| .fields | |
| .iter() | |
| .map(|field| { | |
| let ty = cx.tcx.type_of(field.did).instantiate(cx.tcx, args); | |
| // normalize the type to only show concrete types | |
| // note: we do not use try_normalize_erasing_regions since we | |
| // do care about showing the regions | |
| let ty = infcx | |
| .at(&ObligationCause::dummy(), cx.param_env) | |
| .query_normalize(ty) | |
| .map(|normalized| normalized.value) | |
| .unwrap_or(ty); | |
| clean_field_with_def_id( | |
| field.did, | |
| field.name, | |
| clean_middle_ty(ty::Binder::dummy(ty), cx, Some(field.did), None), | |
| cx, | |
| ) | |
| }) | |
| .collect(), | |
| ), | |
| None => VariantKind::Struct(VariantStruct { | |
| fields: variant | |
| .fields | |
| .iter() | |
| .map(|field| { | |
| let ty = cx.tcx.type_of(field.did).instantiate(cx.tcx, args); | |
| // normalize the type to only show concrete types | |
| // note: we do not use try_normalize_erasing_regions since we | |
| // do care about showing the regions | |
| let ty = infcx | |
| .at(&ObligationCause::dummy(), cx.param_env) | |
| .query_normalize(ty) | |
| .map(|normalized| normalized.value) | |
| .unwrap_or(ty); | |
| clean_field_with_def_id( | |
| field.did, | |
| field.name, | |
| clean_middle_ty(ty::Binder::dummy(ty), cx, Some(field.did), None), | |
| cx, | |
| ) | |
| }) | |
| .collect(), | |
| }), | |
| }; | |
| Item::from_def_id_and_parts( | |
| variant.def_id, | |
| Some(variant.name), | |
| VariantItem(Variant { kind, discriminant }), | |
| cx, | |
| ) | |
| } | |
| fn clean_variant_data<'tcx>( | |
| variant: &hir::VariantData<'tcx>, | |
| disr_expr: &Option<&hir::AnonConst>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Variant { | |
| let discriminant = disr_expr | |
| .map(|disr| Discriminant { expr: Some(disr.body), value: disr.def_id.to_def_id() }); | |
| let kind = match variant { | |
| hir::VariantData::Struct { fields, .. } => VariantKind::Struct(VariantStruct { | |
| fields: fields.iter().map(|x| clean_field(x, cx)).collect(), | |
| }), | |
| hir::VariantData::Tuple(..) => { | |
| VariantKind::Tuple(variant.fields().iter().map(|x| clean_field(x, cx)).collect()) | |
| } | |
| hir::VariantData::Unit(..) => VariantKind::CLike, | |
| }; | |
| Variant { discriminant, kind } | |
| } | |
| fn clean_path<'tcx>(path: &hir::Path<'tcx>, cx: &mut DocContext<'tcx>) -> Path { | |
| Path { | |
| res: path.res, | |
| segments: path.segments.iter().map(|x| clean_path_segment(x, cx)).collect(), | |
| } | |
| } | |
| fn clean_generic_args<'tcx>( | |
| trait_did: Option<DefId>, | |
| generic_args: &hir::GenericArgs<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> GenericArgs { | |
| match generic_args.parenthesized { | |
| hir::GenericArgsParentheses::No => { | |
| let args = generic_args | |
| .args | |
| .iter() | |
| .map(|arg| match arg { | |
| hir::GenericArg::Lifetime(lt) if !lt.is_anonymous() => { | |
| GenericArg::Lifetime(clean_lifetime(lt, cx)) | |
| } | |
| hir::GenericArg::Lifetime(_) => GenericArg::Lifetime(Lifetime::elided()), | |
| hir::GenericArg::Type(ty) => GenericArg::Type(clean_ty(ty.as_unambig_ty(), cx)), | |
| hir::GenericArg::Const(ct) => { | |
| GenericArg::Const(Box::new(clean_const(ct.as_unambig_ct()))) | |
| } | |
| hir::GenericArg::Infer(_inf) => GenericArg::Infer, | |
| }) | |
| .collect(); | |
| let constraints = generic_args | |
| .constraints | |
| .iter() | |
| .map(|c| { | |
| clean_assoc_item_constraint( | |
| trait_did.expect("only trait ref has constraints"), | |
| c, | |
| cx, | |
| ) | |
| }) | |
| .collect::<ThinVec<_>>(); | |
| GenericArgs::AngleBracketed { args, constraints } | |
| } | |
| hir::GenericArgsParentheses::ParenSugar => { | |
| let Some((inputs, output)) = generic_args.paren_sugar_inputs_output() else { | |
| bug!(); | |
| }; | |
| let inputs = inputs.iter().map(|x| clean_ty(x, cx)).collect(); | |
| let output = match output.kind { | |
| hir::TyKind::Tup(&[]) => None, | |
| _ => Some(Box::new(clean_ty(output, cx))), | |
| }; | |
| GenericArgs::Parenthesized { inputs, output } | |
| } | |
| hir::GenericArgsParentheses::ReturnTypeNotation => GenericArgs::ReturnTypeNotation, | |
| } | |
| } | |
| fn clean_path_segment<'tcx>( | |
| path: &hir::PathSegment<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> PathSegment { | |
| let trait_did = match path.res { | |
| hir::def::Res::Def(DefKind::Trait | DefKind::TraitAlias, did) => Some(did), | |
| _ => None, | |
| }; | |
| PathSegment { name: path.ident.name, args: clean_generic_args(trait_did, path.args(), cx) } | |
| } | |
| fn clean_bare_fn_ty<'tcx>( | |
| bare_fn: &hir::FnPtrTy<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> BareFunctionDecl { | |
| let (generic_params, decl) = enter_impl_trait(cx, |cx| { | |
| // NOTE: Generics must be cleaned before params. | |
| let generic_params = bare_fn | |
| .generic_params | |
| .iter() | |
| .filter(|p| !is_elided_lifetime(p)) | |
| .map(|x| clean_generic_param(cx, None, x)) | |
| .collect(); | |
| // Since it's more conventional stylistically, elide the name of all params called `_` | |
| // unless there's at least one interestingly named param in which case don't elide any | |
| // name since mixing named and unnamed params is less legible. | |
| let filter = |ident: Option<Ident>| { | |
| ident.map(|ident| ident.name).filter(|&ident| ident != kw::Underscore) | |
| }; | |
| let fallback = | |
| bare_fn.param_idents.iter().copied().find_map(filter).map(|_| kw::Underscore); | |
| let params = clean_params(cx, bare_fn.decl.inputs, bare_fn.param_idents, |ident| { | |
| filter(ident).or(fallback) | |
| }); | |
| let decl = clean_fn_decl_with_params(cx, bare_fn.decl, None, params); | |
| (generic_params, decl) | |
| }); | |
| BareFunctionDecl { safety: bare_fn.safety, abi: bare_fn.abi, decl, generic_params } | |
| } | |
| fn clean_unsafe_binder_ty<'tcx>( | |
| unsafe_binder_ty: &hir::UnsafeBinderTy<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> UnsafeBinderTy { | |
| let generic_params = unsafe_binder_ty | |
| .generic_params | |
| .iter() | |
| .filter(|p| !is_elided_lifetime(p)) | |
| .map(|x| clean_generic_param(cx, None, x)) | |
| .collect(); | |
| let ty = clean_ty(unsafe_binder_ty.inner_ty, cx); | |
| UnsafeBinderTy { generic_params, ty } | |
| } | |
| pub(crate) fn reexport_chain( | |
| tcx: TyCtxt<'_>, | |
| import_def_id: LocalDefId, | |
| target_def_id: DefId, | |
| ) -> &[Reexport] { | |
| for child in tcx.module_children_local(tcx.local_parent(import_def_id)) { | |
| if child.res.opt_def_id() == Some(target_def_id) | |
| && child.reexport_chain.first().and_then(|r| r.id()) == Some(import_def_id.to_def_id()) | |
| { | |
| return &child.reexport_chain; | |
| } | |
| } | |
| &[] | |
| } | |
| /// Collect attributes from the whole import chain. | |
| fn get_all_import_attributes<'hir>( | |
| cx: &mut DocContext<'hir>, | |
| import_def_id: LocalDefId, | |
| target_def_id: DefId, | |
| is_inline: bool, | |
| ) -> Vec<(Cow<'hir, hir::Attribute>, Option<DefId>)> { | |
| let mut attrs = Vec::new(); | |
| let mut first = true; | |
| for def_id in reexport_chain(cx.tcx, import_def_id, target_def_id) | |
| .iter() | |
| .flat_map(|reexport| reexport.id()) | |
| { | |
| let import_attrs = inline::load_attrs(cx, def_id); | |
| if first { | |
| // This is the "original" reexport so we get all its attributes without filtering them. | |
| attrs = import_attrs.iter().map(|attr| (Cow::Borrowed(attr), Some(def_id))).collect(); | |
| first = false; | |
| // We don't add attributes of an intermediate re-export if it has `#[doc(hidden)]`. | |
| } else if cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id) { | |
| add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id)); | |
| } | |
| } | |
| attrs | |
| } | |
| /// When inlining items, we merge their attributes (and all the reexports attributes too) with the | |
| /// final reexport. For example: | |
| /// | |
| /// ```ignore (just an example) | |
| /// #[doc(hidden, cfg(feature = "foo"))] | |
| /// pub struct Foo; | |
| /// | |
| /// #[doc(cfg(feature = "bar"))] | |
| /// #[doc(hidden, no_inline)] | |
| /// pub use Foo as Foo1; | |
| /// | |
| /// #[doc(inline)] | |
| /// pub use Foo2 as Bar; | |
| /// ``` | |
| /// | |
| /// So `Bar` at the end will have both `cfg(feature = "...")`. However, we don't want to merge all | |
| /// attributes so we filter out the following ones: | |
| /// * `doc(inline)` | |
| /// * `doc(no_inline)` | |
| /// * `doc(hidden)` | |
| fn add_without_unwanted_attributes<'hir>( | |
| attrs: &mut Vec<(Cow<'hir, hir::Attribute>, Option<DefId>)>, | |
| new_attrs: &'hir [hir::Attribute], | |
| is_inline: bool, | |
| import_parent: Option<DefId>, | |
| ) { | |
| for attr in new_attrs { | |
| match attr { | |
| hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => { | |
| attrs.push((Cow::Borrowed(attr), import_parent)); | |
| } | |
| hir::Attribute::Parsed(AttributeKind::Doc(box d)) => { | |
| // Remove attributes from `normal` that should not be inherited by `use` re-export. | |
| let DocAttribute { | |
| aliases, | |
| hidden, | |
| inline, | |
| cfg, | |
| auto_cfg: _, | |
| auto_cfg_change: _, | |
| fake_variadic: _, | |
| keyword: _, | |
| attribute: _, | |
| masked: _, | |
| notable_trait: _, | |
| search_unbox: _, | |
| html_favicon_url: _, | |
| html_logo_url: _, | |
| html_playground_url: _, | |
| html_root_url: _, | |
| html_no_source: _, | |
| issue_tracker_base_url: _, | |
| rust_logo: _, | |
| test_attrs: _, | |
| no_crate_inject: _, | |
| } = d; | |
| let mut attr = DocAttribute::default(); | |
| if is_inline { | |
| attr.cfg = cfg.clone(); | |
| } else { | |
| attr.inline = inline.clone(); | |
| attr.hidden = hidden.clone(); | |
| } | |
| attr.aliases = aliases.clone(); | |
| attrs.push(( | |
| Cow::Owned(hir::Attribute::Parsed(AttributeKind::Doc(Box::new(attr)))), | |
| import_parent, | |
| )); | |
| } | |
| // We discard `#[cfg(...)]` attributes unless we're inlining | |
| hir::Attribute::Parsed(AttributeKind::CfgTrace(..)) if !is_inline => {} | |
| // We keep all other attributes | |
| _ => { | |
| attrs.push((Cow::Borrowed(attr), import_parent)); | |
| } | |
| } | |
| } | |
| } | |
| fn clean_maybe_renamed_item<'tcx>( | |
| cx: &mut DocContext<'tcx>, | |
| item: &hir::Item<'tcx>, | |
| renamed: Option<Symbol>, | |
| import_ids: &[LocalDefId], | |
| ) -> Vec<Item> { | |
| use hir::ItemKind; | |
| fn get_name( | |
| cx: &DocContext<'_>, | |
| item: &hir::Item<'_>, | |
| renamed: Option<Symbol>, | |
| ) -> Option<Symbol> { | |
| renamed.or_else(|| cx.tcx.hir_opt_name(item.hir_id())) | |
| } | |
| let def_id = item.owner_id.to_def_id(); | |
| cx.with_param_env(def_id, |cx| { | |
| // These kinds of item either don't need a `name` or accept a `None` one so we handle them | |
| // before. | |
| match item.kind { | |
| ItemKind::Impl(ref impl_) => { | |
| // If `renamed` is `Some()` for an `impl`, it means it's been inlined because we use | |
| // it as a marker to indicate that this is an inlined impl and that we should | |
| // generate an impl placeholder and not a "real" impl item. | |
| return clean_impl(impl_, item.owner_id.def_id, cx, renamed.is_some()); | |
| } | |
| ItemKind::Use(path, kind) => { | |
| return clean_use_statement( | |
| item, | |
| get_name(cx, item, renamed), | |
| path, | |
| kind, | |
| cx, | |
| &mut FxHashSet::default(), | |
| ); | |
| } | |
| _ => {} | |
| } | |
| let mut name = get_name(cx, item, renamed).unwrap(); | |
| let kind = match item.kind { | |
| ItemKind::Static(mutability, _, ty, body_id) => StaticItem(Static { | |
| type_: Box::new(clean_ty(ty, cx)), | |
| mutability, | |
| expr: Some(body_id), | |
| }), | |
| ItemKind::Const(_, generics, ty, rhs) => ConstantItem(Box::new(Constant { | |
| generics: clean_generics(generics, cx), | |
| type_: clean_ty(ty, cx), | |
| kind: clean_const_item_rhs(rhs, def_id), | |
| })), | |
| ItemKind::TyAlias(_, generics, ty) => { | |
| *cx.current_type_aliases.entry(def_id).or_insert(0) += 1; | |
| let rustdoc_ty = clean_ty(ty, cx); | |
| let type_ = | |
| clean_middle_ty(ty::Binder::dummy(lower_ty(cx.tcx, ty)), cx, None, None); | |
| let generics = clean_generics(generics, cx); | |
| if let Some(count) = cx.current_type_aliases.get_mut(&def_id) { | |
| *count -= 1; | |
| if *count == 0 { | |
| cx.current_type_aliases.remove(&def_id); | |
| } | |
| } | |
| let ty = cx.tcx.type_of(def_id).instantiate_identity(); | |
| let mut ret = Vec::new(); | |
| let inner_type = clean_ty_alias_inner_type(ty, cx, &mut ret); | |
| ret.push(generate_item_with_correct_attrs( | |
| cx, | |
| TypeAliasItem(Box::new(TypeAlias { | |
| generics, | |
| inner_type, | |
| type_: rustdoc_ty, | |
| item_type: Some(type_), | |
| })), | |
| item.owner_id.def_id.to_def_id(), | |
| name, | |
| import_ids, | |
| renamed, | |
| )); | |
| return ret; | |
| } | |
| ItemKind::Enum(_, generics, def) => EnumItem(Enum { | |
| variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(), | |
| generics: clean_generics(generics, cx), | |
| }), | |
| ItemKind::TraitAlias(_, _, generics, bounds) => TraitAliasItem(TraitAlias { | |
| generics: clean_generics(generics, cx), | |
| bounds: bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(), | |
| }), | |
| ItemKind::Union(_, generics, variant_data) => UnionItem(Union { | |
| generics: clean_generics(generics, cx), | |
| fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(), | |
| }), | |
| ItemKind::Struct(_, generics, variant_data) => StructItem(Struct { | |
| ctor_kind: variant_data.ctor_kind(), | |
| generics: clean_generics(generics, cx), | |
| fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(), | |
| }), | |
| // FIXME: handle attributes and derives that aren't proc macros, and macros with | |
| // multiple kinds | |
| ItemKind::Macro(_, macro_def, MacroKinds::BANG) => MacroItem(Macro { | |
| source: display_macro_source(cx, name, macro_def), | |
| macro_rules: macro_def.macro_rules, | |
| }), | |
| ItemKind::Macro(_, _, MacroKinds::ATTR) => { | |
| clean_proc_macro(item, &mut name, MacroKind::Attr, cx) | |
| } | |
| ItemKind::Macro(_, _, MacroKinds::DERIVE) => { | |
| clean_proc_macro(item, &mut name, MacroKind::Derive, cx) | |
| } | |
| ItemKind::Macro(_, _, _) => todo!("Handle macros with multiple kinds"), | |
| // proc macros can have a name set by attributes | |
| ItemKind::Fn { ref sig, generics, body: body_id, .. } => { | |
| clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx) | |
| } | |
| ItemKind::Trait(_, _, _, _, generics, bounds, item_ids) => { | |
| let items = item_ids | |
| .iter() | |
| .map(|&ti| clean_trait_item(cx.tcx.hir_trait_item(ti), cx)) | |
| .collect(); | |
| TraitItem(Box::new(Trait { | |
| def_id, | |
| items, | |
| generics: clean_generics(generics, cx), | |
| bounds: bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(), | |
| })) | |
| } | |
| ItemKind::ExternCrate(orig_name, _) => { | |
| return clean_extern_crate(item, name, orig_name, cx); | |
| } | |
| _ => span_bug!(item.span, "not yet converted"), | |
| }; | |
| vec![generate_item_with_correct_attrs( | |
| cx, | |
| kind, | |
| item.owner_id.def_id.to_def_id(), | |
| name, | |
| import_ids, | |
| renamed, | |
| )] | |
| }) | |
| } | |
| fn clean_variant<'tcx>(variant: &hir::Variant<'tcx>, cx: &mut DocContext<'tcx>) -> Item { | |
| let kind = VariantItem(clean_variant_data(&variant.data, &variant.disr_expr, cx)); | |
| Item::from_def_id_and_parts(variant.def_id.to_def_id(), Some(variant.ident.name), kind, cx) | |
| } | |
| fn clean_impl<'tcx>( | |
| impl_: &hir::Impl<'tcx>, | |
| def_id: LocalDefId, | |
| cx: &mut DocContext<'tcx>, | |
| // If true, this is an inlined impl and it will be handled later on in the code. | |
| // In here, we will generate a placeholder for it in order to be able to compute its | |
| // `doc_cfg` info. | |
| is_inlined: bool, | |
| ) -> Vec<Item> { | |
| let tcx = cx.tcx; | |
| let mut ret = Vec::new(); | |
| let trait_ = match impl_.of_trait { | |
| Some(t) => { | |
| if is_inlined { | |
| return vec![Item::from_def_id_and_parts( | |
| def_id.to_def_id(), | |
| None, | |
| PlaceholderImplItem, | |
| cx, | |
| )]; | |
| } | |
| Some(clean_trait_ref(&t.trait_ref, cx)) | |
| } | |
| None => None, | |
| }; | |
| let items = impl_ | |
| .items | |
| .iter() | |
| .map(|&ii| clean_impl_item(tcx.hir_impl_item(ii), cx)) | |
| .collect::<Vec<_>>(); | |
| // 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(|alias_def_id: DefId| match tcx.def_kind(alias_def_id) { | |
| DefKind::TyAlias => Some(clean_middle_ty( | |
| ty::Binder::dummy(tcx.type_of(def_id).instantiate_identity()), | |
| cx, | |
| Some(def_id.to_def_id()), | |
| None, | |
| )), | |
| _ => None, | |
| }); | |
| let is_deprecated = tcx | |
| .lookup_deprecation(def_id.to_def_id()) | |
| .is_some_and(|deprecation| deprecation.is_in_effect()); | |
| let mut make_item = |trait_: Option<Path>, for_: Type, items: Vec<Item>| { | |
| let kind = ImplItem(Box::new(Impl { | |
| safety: match impl_.of_trait { | |
| Some(of_trait) => of_trait.safety, | |
| None => hir::Safety::Safe, | |
| }, | |
| generics: clean_generics(impl_.generics, cx), | |
| trait_, | |
| for_, | |
| items, | |
| polarity: if impl_.of_trait.is_some() { | |
| tcx.impl_polarity(def_id) | |
| } else { | |
| ty::ImplPolarity::Positive | |
| }, | |
| kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), |d| d.fake_variadic.is_some()) { | |
| ImplKind::FakeVariadic | |
| } else { | |
| ImplKind::Normal | |
| }, | |
| is_deprecated, | |
| })); | |
| Item::from_def_id_and_parts(def_id.to_def_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.owner_id.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.owner_id); | |
| let please_inline = ty_vis.is_public() | |
| && attrs.iter().any(|a| { | |
| matches!( | |
| a, | |
| hir::Attribute::Parsed(AttributeKind::Doc(d)) | |
| if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline)) | |
| }) | |
| && !cx.is_json_output(); | |
| let krate_owner_def_id = krate.owner_id.def_id; | |
| if please_inline | |
| && let Some(items) = inline::try_inline( | |
| cx, | |
| Res::Def(DefKind::Mod, crate_def_id), | |
| name, | |
| Some((attrs, Some(krate_owner_def_id))), | |
| &mut Default::default(), | |
| ) | |
| { | |
| return items; | |
| } | |
| vec![Item::from_def_id_and_parts( | |
| krate_owner_def_id.to_def_id(), | |
| Some(name), | |
| ExternCrateItem { src: orig_name }, | |
| cx, | |
| )] | |
| } | |
| fn clean_use_statement<'tcx>( | |
| import: &hir::Item<'tcx>, | |
| name: Option<Symbol>, | |
| path: &hir::UsePath<'tcx>, | |
| kind: hir::UseKind, | |
| cx: &mut DocContext<'tcx>, | |
| inlined_names: &mut FxHashSet<(ItemType, Symbol)>, | |
| ) -> Vec<Item> { | |
| let mut items = Vec::new(); | |
| let hir::UsePath { segments, ref res, span } = *path; | |
| for res in res.present_items() { | |
| let path = hir::Path { segments, res, span }; | |
| items.append(&mut clean_use_statement_inner(import, name, &path, kind, cx, inlined_names)); | |
| } | |
| items | |
| } | |
| fn clean_use_statement_inner<'tcx>( | |
| import: &hir::Item<'tcx>, | |
| name: Option<Symbol>, | |
| path: &hir::Path<'tcx>, | |
| kind: hir::UseKind, | |
| cx: &mut DocContext<'tcx>, | |
| inlined_names: &mut FxHashSet<(ItemType, Symbol)>, | |
| ) -> Vec<Item> { | |
| if should_ignore_res(path.res) { | |
| return Vec::new(); | |
| } | |
| // 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.owner_id); | |
| let attrs = cx.tcx.hir_attrs(import.hir_id()); | |
| let inline_attr = find_attr!( | |
| attrs, | |
| Doc(d) if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline) => d | |
| ) | |
| .and_then(|d| d.inline.first()); | |
| let pub_underscore = visibility.is_public() && name == Some(kw::Underscore); | |
| let current_mod = cx.tcx.parent_module_from_def_id(import.owner_id.def_id); | |
| let import_def_id = import.owner_id.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.to_local_def_id()); | |
| // 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, cx.tcx) && !current_mod.is_top_level_module(); | |
| if pub_underscore && let Some((_, inline_span)) = inline_attr { | |
| struct_span_code_err!( | |
| cx.tcx.dcx(), | |
| *inline_span, | |
| E0780, | |
| "anonymous imports cannot be inlined" | |
| ) | |
| .with_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.is_json_output() | |
| || !(visibility.is_public() || (cx.document_private() && is_visible_from_parent_mod)) | |
| || pub_underscore | |
| || attrs.iter().any(|a| matches!( | |
| a, | |
| hir::Attribute::Parsed(AttributeKind::Doc(d)) | |
| if d.hidden.is_some() || d.inline.first().is_some_and(|(i, _)| *i == DocInline::NoInline) | |
| )); | |
| // 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 = DefIdSet::default(); | |
| if let Some(items) = inline::try_inline_glob( | |
| cx, | |
| path.res, | |
| current_mod, | |
| &mut visited, | |
| inlined_names, | |
| import, | |
| ) { | |
| return items; | |
| } | |
| } | |
| Import::new_glob(resolve_use_source(cx, path), true) | |
| } else { | |
| let name = name.unwrap(); | |
| if inline_attr.is_none() | |
| && let Res::Def(DefKind::Mod, did) = path.res | |
| && !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 Some(mut items) = inline::try_inline( | |
| cx, | |
| path.res, | |
| name, | |
| Some((attrs, Some(import_def_id))), | |
| &mut Default::default(), | |
| ) | |
| { | |
| items.push(Item::from_def_id_and_parts( | |
| import_def_id.to_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>, | |
| import_id: Option<LocalDefId>, | |
| ) -> Item { | |
| let def_id = item.owner_id.to_def_id(); | |
| cx.with_param_env(def_id, |cx| { | |
| let kind = match item.kind { | |
| hir::ForeignItemKind::Fn(sig, idents, generics) => ForeignFunctionItem( | |
| clean_function(cx, &sig, generics, ParamsSrc::Idents(idents)), | |
| sig.header.safety(), | |
| ), | |
| hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem( | |
| Static { type_: Box::new(clean_ty(ty, cx)), mutability, expr: None }, | |
| safety, | |
| ), | |
| hir::ForeignItemKind::Type => ForeignTypeItem, | |
| }; | |
| generate_item_with_correct_attrs( | |
| cx, | |
| kind, | |
| item.owner_id.def_id.to_def_id(), | |
| item.ident.name, | |
| import_id.as_slice(), | |
| renamed, | |
| ) | |
| }) | |
| } | |
| fn clean_assoc_item_constraint<'tcx>( | |
| trait_did: DefId, | |
| constraint: &hir::AssocItemConstraint<'tcx>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> AssocItemConstraint { | |
| AssocItemConstraint { | |
| assoc: PathSegment { | |
| name: constraint.ident.name, | |
| args: clean_generic_args(None, constraint.gen_args, cx), | |
| }, | |
| kind: match constraint.kind { | |
| hir::AssocItemConstraintKind::Equality { ref term } => { | |
| let assoc_tag = match term { | |
| hir::Term::Ty(_) => ty::AssocTag::Type, | |
| hir::Term::Const(_) => ty::AssocTag::Const, | |
| }; | |
| let assoc_item = cx | |
| .tcx | |
| .associated_items(trait_did) | |
| .find_by_ident_and_kind(cx.tcx, constraint.ident, assoc_tag, trait_did) | |
| .map(|item| item.def_id); | |
| AssocItemConstraintKind::Equality { term: clean_hir_term(assoc_item, term, cx) } | |
| } | |
| hir::AssocItemConstraintKind::Bound { bounds } => AssocItemConstraintKind::Bound { | |
| bounds: bounds.iter().filter_map(|b| clean_generic_bound(b, cx)).collect(), | |
| }, | |
| }, | |
| } | |
| } | |
| fn clean_bound_vars<'tcx>( | |
| bound_vars: &ty::List<ty::BoundVariableKind<'tcx>>, | |
| cx: &mut DocContext<'tcx>, | |
| ) -> Vec<GenericParamDef> { | |
| bound_vars | |
| .into_iter() | |
| .filter_map(|var| match var { | |
| ty::BoundVariableKind::Region(ty::BoundRegionKind::Named(def_id)) => { | |
| let name = cx.tcx.item_name(def_id); | |
| if name != kw::UnderscoreLifetime { | |
| Some(GenericParamDef::lifetime(def_id, name)) | |
| } else { | |
| None | |
| } | |
| } | |
| ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id)) => { | |
| let name = cx.tcx.item_name(def_id); | |
| Some(GenericParamDef { | |
| name, | |
| def_id, | |
| kind: GenericParamDefKind::Type { | |
| bounds: ThinVec::new(), | |
| default: None, | |
| synthetic: false, | |
| }, | |
| }) | |
| } | |
| // FIXME(non_lifetime_binders): Support higher-ranked const parameters. | |
| ty::BoundVariableKind::Const => None, | |
| _ => None, | |
| }) | |
| .collect() | |
| } |