It's common to import dependencies from the sysroot via `extern crate` rather than use an explicit cargo dependency, when it's necessary to use the same dependency version as used by rustc itself. However, this is dangerous for crates.io crates, since rustc may not pull in the dependency on some targets, or may pull in multiple versions. In both cases, the `extern crate` fails to resolve. To address this, re-export all such dependencies from the appropriate `rustc_*` crates, and use this alias from crates which would otherwise need to use `extern crate`.
3217 lines
123 KiB
Rust
3217 lines
123 KiB
Rust
//! 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][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_ast::token::{Token, TokenKind};
|
|
use rustc_ast::tokenstream::{TokenStream, TokenTree};
|
|
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::attrs::AttributeKind;
|
|
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::hir_ty_lowering::FeedConstTy;
|
|
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, sym};
|
|
use rustc_trait_selection::traits::wf::object_region_bounds;
|
|
use tracing::{debug, instrument};
|
|
use utils::*;
|
|
use {rustc_ast as ast, rustc_hir as hir};
|
|
|
|
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.render_options.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.render_options.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.render_options.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 =
|
|
hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
|
|
.get_word_attr(sym::inline)
|
|
.is_some()
|
|
|| (is_glob_import(cx.tcx, import_id)
|
|
&& (cx.render_options.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 = cx.tcx.def_kind(trait_ref.def_id()).into();
|
|
if !matches!(kind, ItemType::Trait | ItemType::TraitAlias) {
|
|
span_bug!(cx.tcx.def_span(trait_ref.def_id()), "`TraitRef` had unexpected kind {kind:?}");
|
|
}
|
|
inline::record_extern_fqn(cx, trait_ref.def_id(), kind);
|
|
let path = 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<'tcx>(
|
|
constant: &hir::ConstArg<'tcx>,
|
|
_cx: &mut DocContext<'tcx>,
|
|
) -> ConstantKind {
|
|
match &constant.kind {
|
|
hir::ConstArgKind::Path(qpath) => {
|
|
ConstantKind::Path { path: qpath_to_string(qpath).into() }
|
|
}
|
|
hir::ConstArgKind::Anon(anon) => ConstantKind::Anonymous { body: anon.body },
|
|
hir::ConstArgKind::Infer(..) => ConstantKind::Infer,
|
|
}
|
|
}
|
|
|
|
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>(term: &hir::Term<'tcx>, cx: &mut DocContext<'tcx>) -> Term {
|
|
match term {
|
|
hir::Term::Ty(ty) => Term::Type(clean_ty(ty, cx)),
|
|
hir::Term::Const(c) => {
|
|
let ct = lower_const_arg_for_rustdoc(cx.tcx, c, FeedConstTy::No);
|
|
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)
|
|
.no_bound_vars()
|
|
.expect("const parameter types cannot be generic"),
|
|
),
|
|
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, FeedConstTy::No).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, AttributeKind::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, AttributeKind::ProcMacro(..)) {
|
|
Some(MacroKind::Bang)
|
|
} else if find_attr!(attrs, AttributeKind::ProcMacroDerive { .. }) {
|
|
Some(MacroKind::Derive)
|
|
} else if find_attr!(attrs, AttributeKind::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]) {
|
|
for meta_item_list in attrs
|
|
.iter()
|
|
.filter(|a| a.has_name(sym::rustc_legacy_const_generics))
|
|
.filter_map(|a| a.meta_item_list())
|
|
{
|
|
for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.lit()).enumerate() {
|
|
match literal.kind {
|
|
ast::LitKind::Int(a, _) => {
|
|
let GenericParamDef { name, kind, .. } = func.generics.params.remove(0);
|
|
if let GenericParamDefKind::Const { ty, .. } = kind {
|
|
func.decl.inputs.insert(
|
|
a.get() as _,
|
|
Parameter { name: Some(name), type_: *ty, is_const: true },
|
|
);
|
|
} else {
|
|
panic!("unexpected non const in position {pos}");
|
|
}
|
|
}
|
|
_ => panic!("invalid arg index"),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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: ConstantKind::Local { def_id: local_did, body: default },
|
|
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, None)
|
|
}
|
|
hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => {
|
|
let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Idents(idents));
|
|
RequiredMethodItem(m)
|
|
}
|
|
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: ConstantKind::Local { def_id: local_did, body: expr },
|
|
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, Some(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 provided = match assoc_item.container {
|
|
ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => true,
|
|
ty::AssocContainer::Trait => assoc_item.defaultness(tcx).has_value(),
|
|
};
|
|
if provided {
|
|
let defaultness = match assoc_item.container {
|
|
ty::AssocContainer::TraitImpl(_) => Some(assoc_item.defaultness(tcx)),
|
|
ty::AssocContainer::InherentImpl | ty::AssocContainer::Trait => None,
|
|
};
|
|
MethodItem(item, defaultness)
|
|
} else {
|
|
RequiredMethodItem(item)
|
|
}
|
|
}
|
|
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(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.render_options.document_hidden ||
|
|
!cx.tcx.is_doc_hidden(use_def_id)) &&
|
|
// We never check for "cx.render_options.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_,
|
|
}))
|
|
}
|
|
hir::QPath::LangItem(..) => bug!("clean: requiring documentation of lang item"),
|
|
}
|
|
}
|
|
|
|
fn maybe_expand_private_type_alias<'tcx>(
|
|
cx: &mut DocContext<'tcx>,
|
|
path: &hir::Path<'tcx>,
|
|
) -> Option<Type> {
|
|
let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None };
|
|
// Substitute private type aliases
|
|
let def_id = def_id.as_local()?;
|
|
let alias = if !cx.cache.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::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(..) => "_".to_string(),
|
|
hir::ConstArgKind::Anon(hir::AnonConst { def_id, .. }) => {
|
|
let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, FeedConstTy::No);
|
|
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::Path(..) => {
|
|
let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, FeedConstTy::No);
|
|
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::Typeof(..)
|
|
| 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>(
|
|
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(), cx)))
|
|
}
|
|
hir::GenericArg::Infer(_inf) => GenericArg::Infer,
|
|
})
|
|
.collect();
|
|
let constraints = generic_args
|
|
.constraints
|
|
.iter()
|
|
.map(|c| clean_assoc_item_constraint(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 {
|
|
PathSegment { name: path.ident.name, args: clean_generic_args(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.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id) {
|
|
add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id));
|
|
}
|
|
}
|
|
attrs
|
|
}
|
|
|
|
fn filter_tokens_from_list(
|
|
args_tokens: &TokenStream,
|
|
should_retain: impl Fn(&TokenTree) -> bool,
|
|
) -> Vec<TokenTree> {
|
|
let mut tokens = Vec::with_capacity(args_tokens.len());
|
|
let mut skip_next_comma = false;
|
|
for token in args_tokens.iter() {
|
|
match token {
|
|
TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) if skip_next_comma => {
|
|
skip_next_comma = false;
|
|
}
|
|
token if should_retain(token) => {
|
|
skip_next_comma = false;
|
|
tokens.push(token.clone());
|
|
}
|
|
_ => {
|
|
skip_next_comma = true;
|
|
}
|
|
}
|
|
}
|
|
tokens
|
|
}
|
|
|
|
fn filter_doc_attr_ident(ident: Symbol, is_inline: bool) -> bool {
|
|
if is_inline {
|
|
ident == sym::hidden || ident == sym::inline || ident == sym::no_inline
|
|
} else {
|
|
ident == sym::cfg
|
|
}
|
|
}
|
|
|
|
/// Remove attributes from `normal` that should not be inherited by `use` re-export.
|
|
/// Before calling this function, make sure `normal` is a `#[doc]` attribute.
|
|
fn filter_doc_attr(args: &mut hir::AttrArgs, is_inline: bool) {
|
|
match args {
|
|
hir::AttrArgs::Delimited(args) => {
|
|
let tokens = filter_tokens_from_list(&args.tokens, |token| {
|
|
!matches!(
|
|
token,
|
|
TokenTree::Token(
|
|
Token {
|
|
kind: TokenKind::Ident(
|
|
ident,
|
|
_,
|
|
),
|
|
..
|
|
},
|
|
_,
|
|
) if filter_doc_attr_ident(*ident, is_inline),
|
|
)
|
|
});
|
|
args.tokens = TokenStream::new(tokens);
|
|
}
|
|
hir::AttrArgs::Empty | hir::AttrArgs::Eq { .. } => {}
|
|
}
|
|
}
|
|
|
|
/// 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 {
|
|
if attr.is_doc_comment() {
|
|
attrs.push((Cow::Borrowed(attr), import_parent));
|
|
continue;
|
|
}
|
|
let mut attr = attr.clone();
|
|
match attr {
|
|
hir::Attribute::Unparsed(ref mut normal) if let [ident] = &*normal.path.segments => {
|
|
let ident = ident.name;
|
|
if ident == sym::doc {
|
|
filter_doc_attr(&mut normal.args, is_inline);
|
|
attrs.push((Cow::Owned(attr), import_parent));
|
|
} else if is_inline || ident != sym::cfg_trace {
|
|
// If it's not a `cfg()` attribute, we keep it.
|
|
attrs.push((Cow::Owned(attr), import_parent));
|
|
}
|
|
}
|
|
// FIXME: make sure to exclude `#[cfg_trace]` here when it is ported to the new parsers
|
|
hir::Attribute::Parsed(..) => {
|
|
attrs.push((Cow::Owned(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_) => return clean_impl(impl_, item.owner_id.def_id, cx),
|
|
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, body_id) => ConstantItem(Box::new(Constant {
|
|
generics: clean_generics(generics, cx),
|
|
type_: clean_ty(ty, cx),
|
|
kind: ConstantKind::Local { body: body_id, 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>,
|
|
) -> Vec<Item> {
|
|
let tcx = cx.tcx;
|
|
let mut ret = Vec::new();
|
|
let trait_ = impl_.of_trait.map(|t| clean_trait_ref(&t.trait_ref, cx));
|
|
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 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: tcx.impl_polarity(def_id),
|
|
kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), sym::fake_variadic) {
|
|
ImplKind::FakeVariadic
|
|
} else {
|
|
ImplKind::Normal
|
|
},
|
|
}));
|
|
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| {
|
|
a.has_name(sym::doc)
|
|
&& match a.meta_item_list() {
|
|
Some(l) => ast::attr::list_contains_name(&l, sym::inline),
|
|
None => false,
|
|
}
|
|
})
|
|
&& !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 = hir_attr_lists(attrs, sym::doc).get_word_attr(sym::inline);
|
|
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(ref inline) = 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.render_options.document_private && is_visible_from_parent_mod))
|
|
|| pub_underscore
|
|
|| attrs.iter().any(|a| {
|
|
a.has_name(sym::doc)
|
|
&& match a.meta_item_list() {
|
|
Some(l) => {
|
|
ast::attr::list_contains_name(&l, sym::no_inline)
|
|
|| ast::attr::list_contains_name(&l, sym::hidden)
|
|
}
|
|
None => false,
|
|
}
|
|
});
|
|
|
|
// Also check whether imports were asked to be inlined, in case we're trying to re-export a
|
|
// crate in Rust 2018+
|
|
let path = clean_path(path, cx);
|
|
let inner = if kind == hir::UseKind::Glob {
|
|
if !denied {
|
|
let mut visited = 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>(
|
|
constraint: &hir::AssocItemConstraint<'tcx>,
|
|
cx: &mut DocContext<'tcx>,
|
|
) -> AssocItemConstraint {
|
|
AssocItemConstraint {
|
|
assoc: PathSegment {
|
|
name: constraint.ident.name,
|
|
args: clean_generic_args(constraint.gen_args, cx),
|
|
},
|
|
kind: match constraint.kind {
|
|
hir::AssocItemConstraintKind::Equality { ref term } => {
|
|
AssocItemConstraintKind::Equality { term: clean_hir_term(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>,
|
|
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()
|
|
}
|